¿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?
fuente
¿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?
La diferencia básica es que se usa una función de constructor con la new
palabra clave (lo que hace que JavaScript cree automáticamente un nuevo objeto, se establezca this
dentro 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.
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.
Los detalles de la instanciación se filtran en la API de llamada (a través del new
requisito), 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 new
es 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 new
con un class
constructor, o el constructor arrojará un error.
Si realiza la instanceof
verificación, deja ambigüedad en cuanto a si new
se requiere o no . En mi opinión, no debería ser así. Efectivamente ha cortocircuitado el new
requisito, 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 this
contexto menos flexible .
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 instanceof
que no funciona en contextos de ejecución, y no funciona si su prototipo de constructor se intercambia. También fallará si comienza a regresar this
de 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.
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 this
comporte mal, vea el siguiente punto).
this
se comporta como lo haría normalmente, por lo que puede usarlo para acceder al objeto primario (por ejemplo, en el interior player.create()
, se this
refiere player
, como cualquier otra invocación de método, call
y apply
tambié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.
new
no se comporta como se esperaba (ver arriba). Solución: no lo use.
this
no 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 this
refiere a foo
, al igual que cualquier otro método de JavaScript, vea los beneficios).
new
viola el principio abierto / cerrado. Visite medium.com/javascript-scene/… para una discusión mucho más amplia que la que permiten estos comentarios.
new
palabra clave, no creo que la new
palabra 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.
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.
function User(name) {
this.name = name;
this.isAdmin = false;
}
let user = new User("Jack");
new
crea un objeto prototipo User.prototype
y llama User
con el objeto creado como su this
valor.
new
trata una expresión de argumento para su operando como opcional:
let user = new User;
causaría new
llamar User
sin argumentos.
new
devuelve 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.
Los objetos creados por las funciones de constructor heredan propiedades de la propiedad del constructor prototype
y devuelven verdadero usando el instanceOf
operador en la función de constructor.
Los comportamientos anteriores pueden fallar si cambia dinámicamente el valor de la prototype
propiedad del constructor después de haber usado el constructor. Hacerlo es raro , y no se puede cambiar si el constructor se creó con la class
palabra clave.
Las funciones de constructor se pueden ampliar utilizando la extends
palabra clave.
Las funciones del constructor no pueden regresar null
como un valor de error. Como no es un tipo de datos de objeto, es ignorado por new
.
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.
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 null
como 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 prototype
propiedad de la función de fábrica y regresan false
de instanceOf factoryFunction
.
La función de fábrica no se puede extender de forma segura utilizando la extends
palabra clave porque los objetos extendidos heredarían de la prototype
propiedad de funciones de fábrica en lugar de la prototype
propiedad del constructor utilizado por la función de fábrica.
Las fábricas son "siempre" mejores. Cuando se usan lenguajes orientados a objetos, entonces
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.
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.
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.
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 new
palabras clave.
someMethod
los 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 hacevar obj = { ... , someMethod: function() {}, ... }
, eso llevaría a que cada objeto devuelto mantenga una copia diferente de lasomeMethod
cual es algo que podríamos no querer. Ahí es donde usarnew
yprototype
dentro de la función de fábrica ayudaría.new
con 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.new
internamente oObject.create()
para crear un objeto con un prototipo específico.