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

Alguém pode esclarecer a diferença entre uma function de construtor e uma function de fábrica em JavaScript.

Quando usar um em vez do outro?

A diferença básica é que uma function construtora é usada com a new palavra-chave (que faz com que o JavaScript crie automaticamente um novo object, defina this dentro da function para aquele object e retorne o object):

 var objFromConstructor = new ConstructorFunction(); 

Uma function de fábrica é chamada como uma function “regular”:

 var objFromFactory = factoryFunction(); 

Mas, para ser considerado uma “fábrica”, precisaria retornar uma nova instância de algum object: você não a chamaria de “fábrica” ​​se retornasse um booleano ou algo assim. Isso não acontece automaticamente com o new , mas permite mais flexibilidade em 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 simples exemplo.

Algumas pessoas preferem usar funções de fábrica para tudo, porque elas não gostam de ter que lembrar de usar o new (EDIT: e isso pode ser um problema, porque sem o new a function ainda será executada, mas não como esperado). Eu não vejo isso como uma vantagem: o new é uma parte essencial da linguagem, então para mim deliberadamente evitá-lo é um pouco arbitrário – assim como evitar outras palavras-chave como else .

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

Benefícios do uso de construtores

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

  • this se refere ao novo object

  • Algumas pessoas gostam do jeito var myFoo = new Foo(); lê.

Desvantagens

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

  • Esquecendo-se de 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 o new com um construtor de class , ou o construtor irá lançar um erro.

  • Se você fizer a verificação de instanceof , isso deixará a ambigüidade quanto à necessidade ou não de new . Na minha opinião, não deveria ser. Você efetivamente entrou em curto-circuito com o new requisito, o que significa que você pode apagar a desvantagem nº 1. Mas então você acaba de ter uma function de fábrica em todos, mas o nome , com clichê adicional, uma letra maiúscula e menos flexível this contexto.

Construtores quebram o Princípio Aberto / Fechado

Mas minha principal preocupação é que viole o princípio aberto / fechado. Você começa exportando um construtor, os usuários começam a usar o construtor e, em seguida, você percebe que precisa da flexibilidade de uma fábrica (por exemplo, para alternar a implementação para usar pools de objects ou para instanciar em contextos de execução ou tem mais flexibilidade de inheritance usando protótipos OO).

Você está preso, no entanto. Você não pode fazer a mudança sem quebrar todo o código que chama seu construtor de new . Você não pode alternar para usar pools de objects para ganhos de desempenho, por exemplo.

Além disso, o uso de construtores fornece uma instanceof enganosa, que não funciona nos contextos de execução, e não funciona se o protótipo do construtor for trocado. Ele também falhará se você começar a retornar this de seu construtor e, em seguida, alternar para exportar um object arbitrário, o que você teria que fazer para habilitar o comportamento de fábrica em seu construtor.

Benefícios do uso de fábricas

  • Menos código – sem clichê requerido.

  • Você pode retornar qualquer object arbitrário e usar qualquer protótipo arbitrário – oferecendo mais flexibilidade para criar vários tipos de objects que implementam a mesma API. Por exemplo, um media player que pode criar instâncias de players em HTML5 e flash ou uma biblioteca de events que pode emitir events DOM ou events de soquete da web. As fábricas também podem instanciar objects em contextos de execução, aproveitar os pools de objects e permitir modelos de inheritance protótipo 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 de new . Não faça (Isso fará com que this se comporte mal, veja o próximo ponto).

  • this se comporta normalmente – então você pode usá-lo para acessar o object pai (por exemplo, dentro de player.create() , this se refere ao player , assim como qualquer outra invocação de método player.create() e apply também reatribui this , como esperado Se você armazenar protótipos no object pai, isso pode ser uma ótima maneira de trocar dinamicamente a funcionalidade e permitir um polymorphism muito flexível para sua instanciação de object.

  • Nenhuma ambiguidade sobre se deve ou não capitalizar. Não faça As ferramentas Lint vão reclamar, e você será tentado a tentar usar o new e, em seguida, desfará o benefício descrito acima.

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

Desvantagens

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

  • this não se refere ao novo object (em vez disso, se o construtor é chamado com notação de ponto ou notação de colchetes, por exemplo, foo.bar () – this se refere a foo – assim como todos os outros methods JavaScript – veja os benefícios).

Um construtor retorna uma instância da class na qual você a chama. Uma function de fábrica pode retornar qualquer coisa. Você usaria uma function de fábrica quando precisar retornar valores arbitrários ou quando uma class tiver um grande processo de instalação.

Fábricas são “sempre” melhores. Ao usar linguagens orientadas a objects,

  1. decidir sobre o contrato (os methods e o que eles farão)
  2. Crie interfaces que exponham esses methods (em javascript você não tem interfaces, então você precisa encontrar alguma 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 objects reais criados com o novo) não são expostas ao usuário / consumidor de fábrica. Isso significa que o desenvolvedor da fábrica pode expandir e criar novas implementações, desde que não quebre o contrato … e permite que o consumidor da fábrica se beneficie apenas da nova API sem precisar alterar seu código … se eles usaram um novo e uma “nova” implementação vem então eles têm que ir e mudar cada linha que usa “novo” 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 desta ideia.

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

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

Vale a pena notar que os construtores de 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 localizável e agrupamento / armazenamento de objects.