Función de constructor vs funciones de fábrica

150

¿Alguien puede aclarar la diferencia entre una función de constructor y una función de fábrica en Javascript.

¿Cuándo usar uno en lugar del otro?

Sinan
fuente

Respuestas:

149

La diferencia básica es que se usa una función de constructor con la newpalabra clave (lo que hace que JavaScript cree automáticamente un nuevo objeto, se establezca thisdentro de la función en ese objeto y devuelva el objeto):

var objFromConstructor = new ConstructorFunction();

Una función de fábrica se llama como una función "regular":

var objFromFactory = factoryFunction();

Pero para que se considere una "fábrica" ​​necesitaría devolver una nueva instancia de algún objeto: no lo llamaría una función de "fábrica" ​​si solo devuelve un valor booleano o algo así. Esto no sucede automáticamente como con new, pero permite más flexibilidad en algunos casos.

En un ejemplo realmente simple, las funciones a las que se hace referencia anteriormente podrían verse así:

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;
}

Por supuesto, puede hacer que las funciones de fábrica sean mucho más complicadas que ese simple ejemplo.

Una ventaja de las funciones de fábrica es cuando el objeto a devolver podría ser de varios tipos diferentes, dependiendo de algún parámetro.

nnnnnn
fuente
14
"(EDITAR: y esto puede ser un problema porque sin nuevo la función seguirá ejecutándose pero no como se esperaba)". Esto es solo un problema si intenta llamar a una función de fábrica con "nuevo" o si intenta usar la palabra clave "este" para asignar a la instancia. De lo contrario, simplemente crea un nuevo objeto arbitrario y lo devuelve. Sin problemas, solo una forma diferente y más flexible de hacer las cosas, con menos repetitivo y sin filtrar detalles de instanciación en la API.
Eric Elliott
66
Quería señalar que los ejemplos para ambos casos (función de constructor versus función de fábrica) deberían ser consistentes. El ejemplo de la función de fábrica no incluye someMethodlos objetos devueltos por la fábrica, y ahí es donde se pone un poco nublado. Dentro de la función de fábrica, si uno solo lo hace var obj = { ... , someMethod: function() {}, ... }, eso llevaría a que cada objeto devuelto mantenga una copia diferente de la someMethodcual es algo que podríamos no querer. Ahí es donde usar newy prototypedentro de la función de fábrica ayudaría.
Bharat Khatri
3
Como ya mencionó, algunas personas intentan usar funciones de fábrica simplemente porque no tienen la intención de dejar errores donde las personas se olvidan de usar newcon la función de constructor; Pensé que es donde uno podría necesitar ver un ejemplo de cómo reemplazar constructores con funciones de fábrica y ahí es donde pensé que se requería la coherencia en los ejemplos. De todos modos, la respuesta es suficientemente informativa. Este era solo un punto que quería plantear, no es que esté reduciendo la calidad de la respuesta de ninguna manera.
Bharat Khatri
44
Para mí, la mayor ventaja de las funciones de Factory es que obtienes una mejor encapsulación y ocultación de datos que pueden ser útiles en algunas aplicaciones. Si no hay ningún problema en hacer que cada propiedad de la instancia y los métodos sean públicos y fácilmente modificables por los usuarios, entonces supongo que la función Constructor es más apropiada a menos que no les guste la palabra clave "nueva" como hacen algunas personas.
devius
1
@Federico: los métodos de fábrica no tienen que devolver solo un objeto simple. Se pueden usar newinternamente o Object.create()para crear un objeto con un prototipo específico.
nnnnnn
109

Beneficios de usar constructores

  • La mayoría de los libros te enseñan a usar constructores y new

  • this se refiere al nuevo objeto

  • A algunas personas les gusta la forma de var myFoo = new Foo();leer.

Inconvenientes

  • Los detalles de la instanciación se filtran en la API de llamada (a través del newrequisito), por lo que todas las personas que llaman están estrechamente vinculadas a la implementación del constructor. Si alguna vez necesita la flexibilidad adicional de la fábrica, tendrá que refactorizar a todas las personas que llaman (ciertamente, el caso excepcional, en lugar de la regla).

  • Olvidar newes un error tan común, debe considerar agregar una verificación repetitiva para asegurarse de que el constructor se llame correctamente ( if (!(this instanceof Foo)) { return new Foo() }). EDITAR: desde ES6 (ES2015) no se puede olvidar newcon un classconstructor, o el constructor arrojará un error.

  • Si realiza la instanceofverificación, deja ambigüedad en cuanto a si newse requiere o no . En mi opinión, no debería ser así. Efectivamente ha cortocircuitado el newrequisito, lo que significa que podría borrar el inconveniente # 1. Pero entonces tienes una función de fábrica en todo menos en nombre , con repetitivo adicional, una letra mayúscula y un thiscontexto menos flexible .

Los constructores rompen el principio abierto / cerrado

Pero mi principal preocupación es que viola el principio abierto / cerrado. Comienza exportando un constructor, los usuarios comienzan a usar el constructor, luego, en el futuro, se da cuenta de que necesita la flexibilidad de una fábrica, en su lugar (por ejemplo, para cambiar la implementación para usar grupos de objetos, o para crear instancias en contextos de ejecución, o para tener más flexibilidad de herencia usando prototipado OO).

Sin embargo, estás atrapado. No puede realizar el cambio sin romper todo el código que llama a su constructor new. No puede cambiar a usar agrupaciones de objetos para aumentar el rendimiento, por ejemplo.

Además, el uso de constructores le da un engaño instanceofque no funciona en contextos de ejecución, y no funciona si su prototipo de constructor se intercambia. También fallará si comienza a regresar thisde su constructor, y luego cambia a exportar un objeto arbitrario, lo que tendría que hacer para habilitar un comportamiento de fábrica en su constructor.

Beneficios de usar fábricas

  • Menos código: no se requiere repetitivo.

  • Puede devolver cualquier objeto arbitrario y usar cualquier prototipo arbitrario, lo que le brinda más flexibilidad para crear varios tipos de objetos que implementan la misma API. Por ejemplo, un reproductor multimedia que puede crear instancias de HTML5 y reproductores flash, o una biblioteca de eventos que puede emitir eventos DOM o eventos de socket web. Las fábricas también pueden crear instancias de objetos en contextos de ejecución, aprovechar las agrupaciones de objetos y permitir modelos de herencia de prototipos más flexibles.

  • Nunca necesitaría convertir de una fábrica a un constructor, por lo que la refactorización nunca será un problema.

  • No hay ambigüedad sobre el uso new. No lo hagas (Hará que se thiscomporte mal, vea el siguiente punto).

  • thisse comporta como lo haría normalmente, por lo que puede usarlo para acceder al objeto primario (por ejemplo, en el interior player.create(), se thisrefiere player, como cualquier otra invocación de método, cally applytambién reasignar this, como se esperaba. Si almacena prototipos en el objeto primario, eso puede ser una excelente manera de intercambiar dinámicamente la funcionalidad y permitir un polimorfismo muy flexible para la creación de instancias de su objeto.

  • No hay ambigüedad sobre si capitalizar o no. No lo hagas Las herramientas de pelusa se quejarán, y luego sentirás la tentación de intentar usarlas new, y luego deshacerás el beneficio descrito anteriormente.

  • A algunas personas les gusta el camino var myFoo = foo();o las var myFoo = foo.create();lecturas.

Inconvenientes

  • newno se comporta como se esperaba (ver arriba). Solución: no lo use.

  • thisno se refiere al nuevo objeto (en cambio, si el constructor se invoca con notación de punto o notación de corchetes, por ejemplo, foo.bar () - se thisrefiere a foo, al igual que cualquier otro método de JavaScript, vea los beneficios).

Eric Elliott
fuente
2
¿En qué sentido quiere decir que los constructores hacen que las personas que llaman estén estrechamente acopladas a su implementación? En lo que respecta a los argumentos del constructor, será necesario pasarlos incluso a la función de fábrica para que los use e invoque el constructor apropiado.
Bharat Khatri
44
Con respecto a la violación de Abierto / Cerrado: ¿no se trata todo esto de la inyección de dependencia? Si A necesita B, si A llama a B () nueva o A llama a BFactory.create (), ambos introducen el acoplamiento. Si, por otro lado, le das a A una instancia de B en la raíz de la composición, A no necesita saber nada sobre cómo se instancia B. Siento que tanto los constructores como las fábricas tienen sus usos; los constructores son para instanciación simple, fábricas para instanciación más compleja. Pero en ambos casos, inyectar sus dependencias es sabio.
Stefan Billiet
1
DI es bueno para inyectar estado: configuración, objetos de dominio, etc. Es excesivo para todo lo demás.
Eric Elliott
1
El alboroto es que exigir newviola el principio abierto / cerrado. Visite medium.com/javascript-scene/… para una discusión mucho más amplia que la que permiten estos comentarios.
Eric Elliott
3
Debido a que cualquier función puede devolver un nuevo objeto en JavaScript, y muchos de ellos lo hacen sin la newpalabra clave, no creo que la newpalabra clave realmente proporcione ninguna legibilidad adicional. En mi opinión, parece una tontería saltar a través de los aros para permitir a las personas que llaman escribir más.
Eric Elliott
39

Un constructor devuelve una instancia de la clase a la que la llama. Una función de fábrica puede devolver cualquier cosa. Usaría una función de fábrica cuando necesite devolver valores arbitrarios o cuando una clase tenga un proceso de configuración grande.

Ignacio Vazquez-Abrams
fuente
5

Un ejemplo de función de constructor

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

let user = new User("Jack");
  • newcrea un objeto prototipo User.prototypey llama Usercon el objeto creado como su thisvalor.

  • new trata una expresión de argumento para su operando como opcional:

         let user = new User;

    causaría newllamar Usersin argumentos.

  • newdevuelve el objeto que creó, a menos que el constructor devuelva un valor de objeto , que se devuelve en su lugar. Este es un caso extremo que en su mayor parte puede ignorarse.

Pros y contras

Los objetos creados por las funciones de constructor heredan propiedades de la propiedad del constructor prototypey devuelven verdadero usando el instanceOfoperador en la función de constructor.

Los comportamientos anteriores pueden fallar si cambia dinámicamente el valor de la prototypepropiedad del constructor después de haber usado el constructor. Hacerlo es raro , y no se puede cambiar si el constructor se creó con la classpalabra clave.

Las funciones de constructor se pueden ampliar utilizando la extendspalabra clave.

Las funciones del constructor no pueden regresar nullcomo un valor de error. Como no es un tipo de datos de objeto, es ignorado por new.

Un ejemplo de función de fábrica

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

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

Aquí la función de fábrica se llama sin new. La función es totalmente responsable del uso directo o indirecto si sus argumentos y el tipo de objeto que devuelve. En este ejemplo, devuelve un [Objeto objeto] simple con algunas propiedades establecidas a partir de argumentos.

Pros y contras

Oculta fácilmente las complejidades de implementación de la creación de objetos de la persona que llama. Esto es particularmente útil para las funciones de código nativo en un navegador.

La función de fábrica no siempre necesita devolver objetos del mismo tipo, e incluso podría regresar nullcomo un indicador de error.

En casos simples, las funciones de fábrica pueden ser simples en estructura y significado.

Los objetos devueltos generalmente no heredan de la prototypepropiedad de la función de fábrica y regresan falsede instanceOf factoryFunction.

La función de fábrica no se puede extender de forma segura utilizando la extendspalabra clave porque los objetos extendidos heredarían de la prototypepropiedad de funciones de fábrica en lugar de la prototypepropiedad del constructor utilizado por la función de fábrica.

traktor53
fuente
1
Esta es una respuesta tardía publicada en respuesta a esta pregunta sobre el mismo tema,
traktor53
no solo "nulo" sino que el "nuevo" también ignorará cualquier tipo de datos premitivo devuelto por la función de constructor.
Vishal
2

Las fábricas son "siempre" mejores. Cuando se usan lenguajes orientados a objetos, entonces

  1. decidir sobre el contrato (los métodos y lo que harán)
  2. Cree interfaces que expongan esos métodos (en javascript no tiene interfaces, por lo que debe encontrar alguna forma de verificar la implementación)
  3. Cree una fábrica que devuelva una implementación de cada interfaz requerida.

Las implementaciones (los objetos reales creados con nuevos) no están expuestos al usuario / consumidor de fábrica. Esto significa que el desarrollador de la fábrica puede expandirse y crear nuevas implementaciones siempre que no rompa el contrato ... y permite que el consumidor de la fábrica se beneficie de la nueva API sin tener que cambiar su código ... si usaron una nueva y aparece una "nueva" implementación, entonces tienen que ir y cambiar cada línea que usa "nueva" para usar la "nueva" implementación ... con la fábrica, su código no cambia ...

Fábricas, mejor que cualquier otra cosa, el marco de resorte está completamente construido alrededor de esta idea.

Colin Saxton
fuente
¿Cómo resuelve la fábrica este problema de tener que cambiar cada línea?
Nombre
0

Las fábricas son una capa de abstracción y, como todas las abstracciones, tienen un costo complejo. Al encontrar una API basada en la fábrica, descubrir cuál es la fábrica para una API determinada puede ser un desafío para el consumidor de API. Con los constructores, la capacidad de descubrimiento es trivial.

Al decidir entre fábricas y fábricas, debe decidir si la complejidad está justificada por el beneficio.

Vale la pena señalar que los constructores de Javascript pueden ser fábricas arbitrarias al devolver algo diferente a esto o indefinido. Por lo tanto, en js puede obtener lo mejor de ambos mundos: API reconocible y agrupación / almacenamiento en caché de objetos.

PeterH
fuente
55
En JavaScript, el costo de usar constructores es más alto que el costo de usar fábricas porque cualquier función en JS puede devolver un nuevo objeto. Los constructores agregan complejidad al: Requerir new, Alterar el comportamiento de this, Alterar el valor de retorno, Conectar un prototipo de referencia, Habilitar instanceof(que yace y no debe usarse para este propósito). Aparentemente, todas esas son "características". En la práctica, dañan la calidad de su código.
Eric Elliott
0

Por las diferencias, Eric Elliott aclaró muy bien:

Pero para la segunda pregunta:

¿Cuándo usar uno en lugar del otro?

Si viene del fondo orientado a objetos, la función Constructor le parece más natural. de esta manera no debes olvidarte de usar newpalabras clave.

Mostafa
fuente