Função de construtor versus funções de fábrica

150

Alguém pode esclarecer a diferença entre uma função de construtor e uma função de fábrica em Javascript.

Quando usar um em vez do outro?

Sinan
fonte

Respostas:

149

A diferença básica é que uma função construtora é usada com a newpalavra - chave (que faz com que o JavaScript crie automaticamente um novo objeto, defina thisdentro da função para esse objeto e retorne o objeto):

var objFromConstructor = new ConstructorFunction();

Uma função de fábrica é chamada como uma função "regular":

var objFromFactory = factoryFunction();

Mas, para ser considerada uma "fábrica", seria necessário retornar uma nova instância de algum objeto: você não a chamaria de função "fábrica" ​​se apenas retornasse um valor booleano ou algo assim. Isso não acontece automaticamente como com new, mas permite mais flexibilidade para alguns casos.

Em um exemplo muito simples, as funções mencionadas acima podem ser algo como isto:

function ConstructorFunction() {
   this.someProp1 = "1";
   this.someProp2 = "2";
}
ConstructorFunction.prototype.someMethod = function() { /* whatever */ };

function factoryFunction() {
   var obj = {
      someProp1 : "1",
      someProp2 : "2",
      someMethod: function() { /* whatever */ }
   };
   // other code to manipulate obj in some way here
   return obj;
}

É claro que você pode tornar as funções de fábrica muito mais complicadas do que esse exemplo simples.

Uma vantagem das funções de fábrica é quando o objeto a ser retornado pode ter vários tipos diferentes, dependendo de algum parâmetro.

nnnnnn
fonte
14
"(EDIT: e isso pode ser um problema porque, sem novas, a função ainda será executada, mas não conforme o esperado)." Isso é apenas um problema se você tentar chamar uma função de fábrica com "novo" ou tentar usar a palavra-chave "this" para atribuir à instância. Caso contrário, você simplesmente cria um novo objeto arbitrário e o devolve. Sem problemas, apenas uma maneira diferente e mais flexível de fazer as coisas, com menos clichê e sem vazar detalhes da instanciação na API.
Eric Elliott
6
Queria ressaltar que os exemplos para os dois casos (função construtora x função fábrica) devem ser consistentes. O exemplo da função de fábrica não inclui someMethodos objetos devolvidos pela fábrica e é aí que fica um pouco nebuloso. Dentro da função de fábrica, se alguém o fizer var obj = { ... , someMethod: function() {}, ... }, isso levaria a cada objeto retornado, mantenha uma cópia diferente da someMethodqual é algo que talvez não desejemos. É aí que o uso newe prototypea função de fábrica ajudariam.
Bharat Khatri
3
Como você já mencionou, algumas pessoas tentam usar funções de fábrica apenas porque não pretendem deixar erros, onde as pessoas esquecem de usar newcom a função construtora; Eu pensei que era onde seria necessário ver um exemplo de como substituir construtores por funções de fábrica e é aí que eu achava que a consistência nos exemplos era necessária. De qualquer forma, a resposta é informativa o suficiente. Esse era apenas um ponto que eu queria levantar, não que eu estivesse diminuindo a qualidade da resposta de alguma forma.
Bharat Khatri
4
Para mim, a maior vantagem das funções Factory é que você obtém melhor encapsulamento e ocultação de dados, o que pode ser útil em alguns aplicativos. Se não houver problema em tornar todas as propriedades e métodos de instância públicas e facilmente modificáveis ​​pelos usuários, acho que a função Constructor é mais apropriada, a menos que você não goste da palavra-chave "new" como algumas pessoas fazem.
devius 29/09/14
1
@Federico - os métodos de fábrica não precisam retornar apenas um objeto simples. Eles podem usar newinternamente ou Object.create()criar um objeto com um protótipo específico.
Nnnnnn
109

Benefícios do uso de construtores

  • A maioria dos livros ensina você a usar construtores e new

  • this refere-se ao novo objeto

  • Algumas pessoas gostam da maneira como var myFoo = new Foo();lê.

Desvantagens

  • Os detalhes da instanciação vazam para a API de chamada (por meio do newrequisito), para que todos os chamadores estejam fortemente acoplados à implementação do construtor. Se você precisar da flexibilidade adicional da fábrica, precisará refatorar todos os chamadores (reconhecidamente o caso excepcional, e não a regra).

  • Esquecer newé um bug tão comum, você deve considerar fortemente adicionar uma verificação padrão para garantir que o construtor seja chamado corretamente ( if (!(this instanceof Foo)) { return new Foo() }). EDIT: Desde ES6 (ES2015), você não pode esquecer newum classconstrutor, ou o construtor lançará um erro.

  • Se você fizer o instanceof verificação, deixa ambiguidade quanto a ser ou não newnecessário. Na minha opinião, não deveria ser. Você efetivamente interrompeu o newrequisito, o que significa que você pode apagar a desvantagem nº 1. Mas você terá uma função de fábrica com apenas o nome , com clichê adicional, uma letra maiúscula e menos flexívelthis contexto .

Construtores quebram o Princípio Aberto / Fechado

Mas minha principal preocupação é que viole o princípio de abrir / fechar. Você começa a exportar um construtor, os usuários começam a usá-lo e, depois, no caminho, percebe que precisa da flexibilidade de uma fábrica (por exemplo, para alternar a implementação para usar conjuntos de objetos ou para instanciar contextos de execução ou para ter mais flexibilidade de herança usando OO prototípico).

Você está preso, no entanto. Você não pode fazer a alteração sem quebrar todo o código que chama seu construtor comnew . Você não pode mudar para usar pools de objetos para obter ganhos de desempenho, por exemplo.

Além disso, o uso de construtores fornece uma enganosa instanceof que não funciona em contextos de execução e não funciona se o seu protótipo de construtor for trocado. Também falhará se você começar retornandothis do construtor e depois mudar para exportar um objeto arbitrário, o que você teria que fazer para habilitar o comportamento de fábrica no construtor.

Benefícios do uso de fábricas

  • Menos código - não requer clichê.

  • Você pode retornar qualquer objeto arbitrário e usar qualquer protótipo arbitrário - oferecendo mais flexibilidade para criar vários tipos de objetos que implementam a mesma API. Por exemplo, um media player que pode criar instâncias de HTML5 e flash players, ou uma biblioteca de eventos que pode emitir eventos DOM ou soquetes da web. As fábricas também podem instanciar objetos em contextos de execução, tirar proveito dos pools de objetos e permitir modelos de herança prototípica mais flexíveis.

  • Você nunca precisaria converter de uma fábrica para um construtor; portanto, a refatoração nunca será um problema.

  • Nenhuma ambiguidade sobre o uso new. Não. (Isso fará com que thisse comporte mal, veja o próximo ponto).

  • thisse comporta como normalmente faria - para que você possa usá-lo para acessar o objeto pai (por exemplo, inside player.create(), thisrefere-se a player, como qualquer outra invocação de método faria. calle applytambém reatribuirthis , conforme o esperado. Se você armazenar protótipos no objeto pai, isso pode ser uma ótima maneira de trocar dinamicamente a funcionalidade e permitir um polimorfismo muito flexível para a instanciação do objeto.

  • Nenhuma ambiguidade em capitalizar ou não. Não. As ferramentas de cotão reclamarão, e você será tentado a usá new-lo e depois desfará o benefício descrito acima.

  • Algumas pessoas gostam do jeito var myFoo = foo();ou var myFoo = foo.create();leem.

Desvantagens

  • newnão se comporta como o esperado (veja acima). Solução: não use.

  • thisnão se refere ao novo objeto (em vez disso, se o construtor for chamado com notação de ponto ou colchete, por exemplo, foo.bar () - thisrefere-se a foo- assim como qualquer outro método JavaScript - veja os benefícios).

Eric Elliott
fonte
2
Em que sentido você quer dizer que os construtores tornam os chamadores fortemente acoplados à sua implementação? No que diz respeito aos argumentos do construtor, esses precisam ser passados ​​até para a função factory para que sejam utilizados e invoquem o construtor apropriado.
Bharat Khatri
4
Em relação à violação de Aberto / Fechado: não se trata apenas de injeção de dependência? Se A precisar de B, se A chama nova B () ou A chama BFactory.create (), ambos introduzem acoplamento. Se, por outro lado, você fornecer a A uma instância de B na raiz da composição, A não precisará saber nada sobre como B é instanciado. Sinto que construtores e fábricas têm seus usos; construtores são para instanciação simples, fábricas para instanciação mais complexa. Mas em ambos os casos, injetar suas dependências é sábio.
Stefan Billiet
1
A DI é boa para injetar estado: configuração, objetos de domínio e assim por diante. É um exagero para todo o resto.
Eric Elliott
1
O problema é que a exigência newviola o princípio de abrir / fechar. Consulte medium.com/javascript-scene/… para uma discussão muito maior do que esses comentários permitem.
9788 Eric Elliott #
3
Como qualquer função pode retornar um novo objeto em JavaScript, e muitas delas o fazem sem a newpalavra - chave, não acredito que a newpalavra - chave realmente forneça legibilidade adicional. Na IMO, parece bobagem pular os bastidores para permitir que os chamadores digitem mais.
Eric Elliott
39

Um construtor retorna uma instância da classe em que você a chama. Uma função de fábrica pode retornar qualquer coisa. Você usaria uma função de fábrica quando precisar retornar valores arbitrários ou quando uma classe tiver um grande processo de configuração.

Ignacio Vazquez-Abrams
fonte
5

Um exemplo de função do Construtor

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • newcria um objeto com prototipagem User.prototypee chama Usercom o objeto criado como seu thisvalor.

  • new trata uma expressão de argumento para seu operando como opcional:

         let user = new User;

    causaria newa chamada Usersem argumentos.

  • newretorna o objeto que ele criou, a menos que o construtor retorne um valor de objeto , que é retornado. Este é um caso extremo que, na maior parte, pode ser ignorado.

Prós e contras

Objetos criados por funções de construtor herdam propriedades da propriedade do construtor prototypee retornam true usando o instanceOfoperador na função de construtor.

Os comportamentos acima podem falhar se você alterar dinamicamente o valor da prototypepropriedade do construtor depois de já ter usado o construtor. Fazer isso é raro e não pode ser alterado se o construtor tiver sido criado usando a classpalavra - chave.

As funções do construtor podem ser estendidas usando a extendspalavra - chave

As funções do construtor não podem retornar nullcomo um valor de erro. Como não é um tipo de dados do objeto, é ignorado por new.

Um exemplo de função de fábrica

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

Aqui a função de fábrica é chamada sem new . A função é totalmente responsável pelo uso direto ou indireto se seus argumentos e o tipo de objeto que ela retornar. Neste exemplo, ele retorna um simples [Objeto de objeto] com algumas propriedades definidas a partir de argumentos.

Prós e contras

Oculta facilmente as complexidades de implementação da criação de objetos do chamador. Isso é particularmente útil para funções de código nativo em um navegador.

A função de fábrica nem sempre retorna objetos do mesmo tipo e pode até retornar nullcomo um indicador de erro.

Em casos simples, as funções de fábrica podem ser simples em estrutura e significado.

Os objetos retornados geralmente não herdam da prototypepropriedade da função de fábrica e retornam falsede instanceOf factoryFunction.

A função de fábrica não pode ser estendida com segurança usando a extendspalavra - chave, porque os objetos estendidos herdariam da prototypepropriedade de funções de fábrica, e não da prototypepropriedade do construtor usado pela função de fábrica.

traktor53
fonte
1
Esta é uma resposta tardia postada em resposta a esta pergunta sobre o mesmo assunto,
traktor53
não apenas "nulo", mas o "novo" também ignorará qualquer tipo de dados premitativo retornado pela função contratante.
Vishal
2

As fábricas são "sempre" melhores. Ao usar linguagens orientadas a objetos,

  1. decidir sobre o contrato (os métodos e o que eles farão)
  2. Crie interfaces que expõem esses métodos (em javascript, você não possui interfaces, portanto, é necessário criar uma maneira de verificar a implementação)
  3. Crie uma fábrica que retorne uma implementação de cada interface necessária.

As implementações (os objetos reais criados com o novo) não são expostas ao usuário / consumidor da fábrica. Isso significa que o desenvolvedor da fábrica pode expandir e criar novas implementações, desde que não rompa o contrato ... e permite que o consumidor da fábrica se beneficie apenas da nova API sem precisar alterar o código ... se eles usaram new e uma implementação "nova" aparece, eles precisam mudar todas as linhas que usam "new" para usar a "nova" implementação ... com a fábrica, seu código não muda ...

Fábricas - melhor do que qualquer outra coisa - a estrutura da primavera é completamente construída em torno dessa idéia.

Colin Saxton
fonte
Como a fábrica resolve esse problema de ter que mudar todas as linhas?
Codinome Jack
0

As fábricas são uma camada de abstração e, como todas as abstrações, têm um custo de complexidade. Ao encontrar uma API baseada na fábrica, descobrir qual é a fábrica para uma determinada API pode ser um desafio para o consumidor da API. Com os construtores, a descoberta é trivial.

Ao decidir entre os médicos e as fábricas, você precisa decidir se a complexidade é justificada pelo benefício.

Vale ressaltar que os construtores Javascript podem ser fábricas arbitrárias retornando algo diferente disso ou indefinido. Assim, em js, você pode obter o melhor dos dois mundos - API detectável e pool / cache de objetos.

PeterH
fonte
5
Em JavaScript, o custo do uso de construtores é superior ao custo do uso de fábricas, porque qualquer função em JS pode retornar um novo objeto. Os construtores adicionam complexidade: Exigindo new, Alterando o comportamento de this, Alterando o valor de retorno, Conectando um ref de protótipo, Ativando instanceof(que está e não deve ser usado para essa finalidade). Aparentemente, todos esses são "recursos". Na prática, eles prejudicam a qualidade do seu código.
Eric Elliott
0

Para as diferenças, Eric Elliott esclareceu muito bem,

Mas para a segunda pergunta:

Quando usar um em vez do outro?

Se você é proveniente do plano de fundo orientado a objetos, a função Constructor parece mais natural para você. Dessa forma, você não deve esquecer de usar a newpalavra-chave.

Mostafa
fonte