tutorial node ejemplos javascript oop node.js prototypal-inheritance ecmascript-5

node - OO prototípico en JavaScript



prototype javascript tutorial (4)

TL; DR:

¿Necesitamos fábricas / constructores en OO prototípico? ¿Podemos hacer un cambio de paradigma y soltarlo por completo?

El BackStory:

He estado jugando con hacer OO prototípico en JavaScript últimamente y encuentro que el 99% de OO hecho en JavaScript está forzando patrones clásicos de OO en él.

Mi opinión sobre el OO prototípico es que involucra dos cosas. Un prototipo estático de métodos (y datos estáticos) y un enlace de datos. No necesitamos fábricas o constructores.

En JavaScript, estos son literales de Objeto que contienen funciones y Object.create .

Esto significaría que podemos modelar todo como un modelo / prototipo estático y una abstracción de enlace de datos que preferiblemente se engancha directamente en una base de datos al estilo de documento. Es decir, los objetos se sacan de la base de datos y se crean clonando un prototipo con los datos. Esto significa que no hay lógica de constructor, ni fábricas, ni new .

El código de ejemplo:

Un pseudo ejemplo sería:

var Entity = Object.create(EventEmitter, { addComponent: { value: function _addComponent(component) { if (this[component.type] !== undefined) { this.removeComponent(this[component.type]); } _.each(_.functions(component), (function _bind(f) { component[f] = component[f].bind(this); }).bind(this)); component.bindEvents(); Object.defineProperty(this, component.type, { value: component, configurable: true }); this.emit("component:add", this, component); } }, removeComponent: { value: function _removeComponent(component) { component = component.type || component; delete this[component]; this.emit("component:remove", this, component); } } } var entity = Object.create(Entity, toProperties(jsonStore.get(id)))

La explicación menor:

El código particular es detallado porque ES5 es detallado. Entity arriba es un plano / prototipo. Cualquier objeto real con datos se crearía utilizando Object.create(Entity, {...}) .

Los datos reales (en este caso, los componentes) se cargan directamente desde un almacén JSON y se inyectan directamente en la llamada Object.create . Por supuesto, se aplica un patrón similar para crear componentes y solo las propiedades que pasan Object.hasOwnProperty se almacenan en la base de datos.

Cuando se crea una entidad por primera vez, se crea con un {} vacío

Las preguntas reales:

Ahora mis preguntas reales son

  • ¿Ejemplos de código abierto de JS Prototypical OO?
  • ¿Es esta una buena idea?
  • ¿Está en línea con las ideas y conceptos detrás de OOP prototípico?
  • ¿No usará ningún constructor / funciones de fábrica para morderme el culo en algún lado? ¿Podemos realmente salirse con la suya sin usar constructores? ¿Existe alguna limitación al utilizar la metodología anterior donde necesitaríamos fábricas para superarlos?

Ejemplo de un "tipo" estáticamente clonable:

var MyType = { size: Sizes.large, color: Colors.blue, decay: function _decay() { size = Sizes.medium }, embiggen: function _embiggen() { size = Sizes.xlarge }, normal: function _normal() { size = Sizes.normal }, load: function _load( dbObject ) { size = dbObject.size color = dbObject.color } }

Ahora, podríamos clonar este tipo en otro lugar, ¿sí? Claro, necesitaríamos usar var myType = Object.Create(MyType) , pero luego terminamos, ¿sí? Ahora podemos simplemente myType.size y ese es el tamaño de la cosa. O podríamos leer el color, o cambiarlo, etc. No hemos creado un constructor ni nada, ¿verdad?

Si dijiste que no hay un constructor allí, estás equivocado. Déjame mostrarte dónde está el constructor:

// The following var definition is the constructor var MyType = { size: Sizes.large, color: Colors.blue, decay: function _decay() { size = Sizes.medium }, embiggen: function _embiggen() { size = Sizes.xlarge }, normal: function _normal() { size = Sizes.normal }, load: function _load( dbObject ) { size = dbObject.size color = dbObject.color } }

Porque ya nos hemos ido y hemos creado todas las cosas que queríamos y ya hemos definido todo. Eso es todo lo que hace un constructor. Entonces, incluso si solo clonamos / usamos cosas estáticas (que es lo que veo que hacen los fragmentos anteriores), todavía tenemos un constructor. Solo un constructor estático. Al definir un tipo, hemos definido un constructor. La alternativa es este modelo de construcción de objetos:

var MyType = {} MyType.size = Sizes.large

Pero eventualmente querrás usar Object.Create (MyType) y cuando lo hagas, habrás utilizado un objeto estático para crear el objeto objetivo. Y luego se convierte en el mismo que en el ejemplo anterior.


La respuesta corta a su pregunta "¿Necesitamos fábricas / constructores en OO prototípico?" no es. Las fábricas / constructores solo tienen un propósito: inicializar el objeto recién creado (una instancia) a un estado específico.

Dicho esto, a menudo se usa porque algunos objetos necesitan un código de inicialización de algún tipo.

Usemos el código de entidad basado en componentes que proporcionó. Una entidad típica es simplemente una colección de componentes y pocas propiedades:

var BaseEntity = Object.create({}, { /* Collection of all the Entity''s components */ components: { value: {} } /* Unique identifier for the entity instance */ , id: { value: new Date().getTime() , configurable: false , enumerable: true , writable: false } /* Use for debugging */ , createdTime: { value: new Date() , configurable: false , enumerable: true , writable: false } , removeComponent: { value: function() { /* code left out for brevity */ } , enumerable: true , writable: false } , addComponent: { value: function() { /* code left out for brevity */ } , enumerable: true , writable: false } });

Ahora el siguiente código creará nuevas entidades basadas en ''BaseEntity''

function CreateEntity() { var obj = Object.create(BaseEntity); //Output the resulting object''s information for debugging console.log("[" + obj.id + "] " + obj.createdTime + "/n"); return obj; }

Parece lo suficientemente sencillo, hasta que veas las propiedades de referencia:

setTimeout(CreateEntity, 1000); setTimeout(CreateEntity, 2000); setTimeout(CreateEntity, 3000);

productos:

[1309449384033] Thu Jun 30 2011 11:56:24 GMT-0400 (EDT) [1309449384033] Thu Jun 30 2011 11:56:24 GMT-0400 (EDT) [1309449384033] Thu Jun 30 2011 11:56:24 GMT-0400 (EDT)

Entonces, ¿por qué es esto? La respuesta es simple: debido a la herencia basada en prototipos. Cuando creamos los objetos, no había ningún código para establecer las propiedades id y createdTime en la instancia real, como normalmente se hace en constructores / fábricas. Como resultado, cuando se accede a la propiedad, se extrae de la cadena de prototipos, que termina siendo un valor único para todas las entidades.

El argumento para esto es que Object.create () debería pasar el segundo parámetro para establecer estos valores. Mi respuesta sería simplemente: ¿No es básicamente lo mismo que llamar a un constructor o usar una fábrica? Es solo otra manera de establecer el estado de un objeto.

Ahora, con su implementación en la que trata (y con razón) todos los prototipos como una colección de métodos y propiedades estáticos, inicializa el objeto asignando los valores de las propiedades a los datos de una fuente de datos. Puede que no esté utilizando un new o algún tipo de fábrica, pero es un código de inicialización.

Para resumir: en el prototipo de JavaScript OOP - no se necesita nada new - No se necesitan fábricas - Generalmente se necesita un código de inicialización, que normalmente se realiza a través de fábricas new o alguna otra implementación que no se quiere admitir como inicialización de un objeto


No creo que la lógica de constructor / fábrica sea necesaria en absoluto, siempre y cuando cambie su forma de pensar sobre la Programación Orientada a Objetos. En mi reciente exploración del tema, descubrí que la herencia prototípica se presta más a la definición de un conjunto de funciones que usan datos particulares. Este no es un concepto extraño para aquellos entrenados en la herencia clásica, pero el enganche es que estos objetos "parentales" no definen los datos para ser operados.

var animal = { walk: function() { var i = 0, s = ''''; for (; i < this.legs; i++) { s += ''step ''; } console.log(s); }, speak: function() { console.log(this.favoriteWord); } } var myLion = Object.create(animal); myLion.legs = 4; myLion.favoriteWord = ''woof'';

Entonces, en el ejemplo anterior, creamos la funcionalidad que acompaña a un animal, y luego creamos un objeto que tiene esa funcionalidad, junto con los datos necesarios para completar las acciones. Esto se siente incómodo y extraño para cualquiera que esté acostumbrado a la herencia clásica por cualquier período de tiempo. No tiene ninguna de la tibieza borrosa de la jerarquía pública / privada / protegida de la visibilidad de los miembros, y seré el primero en admitir que me pone nervioso.

Además, mi primer instinto, cuando veo la inicialización anterior del objeto myLion es crear una fábrica para animales, así puedo crear leones, tigres y osos (¡oh mi!) Con una simple llamada a función. Y, creo, esa es una forma natural de pensar para la mayoría de los programadores: la verbosidad del código anterior es fea y parece carecer de elegancia. No he decidido si eso se debe simplemente a la formación clásica, o si eso es una falla real del método anterior.

Ahora, a la herencia.

Siempre he entendido que la inercia en JavaScript es difícil. Navegar por los pormenores de la cadena de prototipos no es exactamente claro. Hasta que lo use con Object.create , que elimina toda la redirección basada en función y nueva palabra clave de la ecuación.

Digamos que queríamos extender el objeto animal anterior y hacer un humano.

var human = Object.create(animal) human.think = function() { console.log(''Hmmmm...''); } var myHuman = Object.create(human); myHuman.legs = 2; myHuman.favoriteWord = ''Hello'';

Esto crea un objeto que tiene human como prototipo, que a su vez tiene a animal como prototipo. Suficientemente fácil. Sin errores de dirección, ningún "nuevo objeto con un prototipo igual al prototipo de la función". Simplemente herencia prototípica simple. Es simple y directo. El polimorfismo es fácil también.

human.speak = function() { console.log(this.favoriteWord + '', dudes''); }

Debido a la forma en que funciona la cadena de prototipos, myHuman.speak se encontrará en human antes de que se encuentre en animal , y por lo tanto, nuestro ser humano es un surfista en lugar de un viejo animal aburrido.

Entonces, en conclusión ( TLDR ):

La funcionalidad del constructor pseudoclásico se agregó a JavaScript para que los programadores entrenados en OOP clásica se sintieran más cómodos. No es, de ninguna manera, necesario, pero significa renunciar a conceptos clásicos como la visibilidad del miembro y constructores (tautológicamente).

Lo que obtienes a cambio es flexibilidad y simplicidad. Puede crear "clases" sobre la marcha: cada objeto es, en sí mismo, una plantilla para otros objetos. Establecer valores en objetos secundarios no afectará el prototipo de esos objetos (es decir, si utilicé var child = Object.create(myHuman) , y luego configuro child.walk = ''not yet'' , animal.walk se vería afectado - en realidad, prueba eso).

La simplicidad de la herencia es honestamente alucinante. He leído mucho sobre herencia en JavaScript y he escrito muchas líneas de código para intentar entenderlo. Pero realmente se reduce a objetos heredados de otros objetos . Es tan simple como eso, y todo lo que hace la palabra clave new es complicar eso.

Esta flexibilidad es difícil de usar en toda su extensión, y estoy seguro de que todavía tengo que hacerlo, pero está ahí, y es interesante navegar. Creo que la mayor parte de la razón por la que no se ha utilizado para un gran proyecto es que simplemente no se entiende tan bien como podría y, en mi humilde opinión, estamos encerrados en los patrones de herencia clásicos que todos aprendimos cuando se les enseñó C ++, Java, etc.

Editar

Creo que hice un buen caso contra los constructores. Pero mi argumento contra las fábricas es confuso.

Después de una mayor contemplación, durante la cual volteé varias veces hacia ambos lados de la cerca, he llegado a la conclusión de que las fábricas también son innecesarias. Si al animal (arriba) se le dio la initialize otra función, sería trivial crear e inicializar un nuevo objeto que hereda de un animal .

var myDog = Object.create(animal); myDog.initialize(4, ''Meow'');

Nuevo objeto, inicializado y listo para usar.

@Raynos - Totalmente nerd me metió en este. Debería estar preparándome durante 5 días sin hacer absolutamente nada productivo.


Según su comentario, la pregunta es principalmente "¿es necesario el conocimiento del constructor?" Siento que es.

Un ejemplo de juguete sería almacenar datos parciales . En un conjunto de datos dado en la memoria, cuando persisto, solo puedo elegir almacenar ciertos elementos (ya sea por razones de eficiencia o por la coherencia de los datos, por ejemplo, los valores son inherentemente inútiles una vez que persisten). Tomemos una sesión donde almaceno el nombre de usuario y la cantidad de veces que han hecho clic en el botón de ayuda (a falta de un mejor ejemplo). Cuando persisto esto en mi ejemplo, no uso el número de clics, ya que lo guardo en la memoria ahora, y la próxima vez que cargue los datos (la próxima vez que el usuario inicie sesión o se conecte o lo que sea) inicializaré el valor desde cero (presumiblemente a 0). Este caso de uso particular es un buen candidato para la lógica del constructor.

Aahh, pero siempre podrías Object.create({name:''Bob'', clicks:0}); en el prototipo estático: Object.create({name:''Bob'', clicks:0}); Claro, en este caso. Pero ¿y si el valor no siempre fuera 0 al principio, sino que era algo que requería computación? Uummmm, por ejemplo, los usuarios envejecen en segundos (suponiendo que hayamos almacenado el nombre y la fecha de nacimiento). De nuevo, un ítem que persiste poco, ya que tendrá que ser recalculado en la recuperación de todos modos. Entonces, ¿cómo se almacena la edad del usuario en el prototipo estático?

La respuesta obvia es la lógica de constructor / inicializador.

Hay muchos más escenarios, aunque no creo que la idea esté muy relacionada con js oop o cualquier lenguaje en particular. La necesidad de la lógica de creación de entidades es inherente a la forma en que veo que los sistemas informáticos modelan el mundo. A veces, los artículos que almacenamos serán una simple recuperación e inyección en un modelo como prototipo de caparazón, y algunas veces los valores son dinámicos y deberán inicializarse.

ACTUALIZAR

Bien, voy a intentar obtener un ejemplo más real, y para evitar confusiones supongamos que no tengo una base de datos y no es necesario que persista ningún dato. Digamos que estoy haciendo un servidor de solitario. Cada nuevo juego será (naturalmente) una nueva instancia del prototipo del Game . Para mí es claro que aquí se requiere su lógica de inicialización (y mucha):

Por ejemplo, necesitaré en cada instancia de juego no solo una baraja de cartas estática / codificada, sino una baraja aleatoriamente mezclada. Si fuera estático, el usuario jugaría el mismo juego cada vez, lo que claramente no es bueno.

También podría tener que iniciar un cronómetro para terminar el juego si el jugador se queda sin dinero. Una vez más, no es algo que pueda ser estático, ya que mi juego tiene algunos requisitos: el número de segundos está inversamente relacionado con la cantidad de juegos que el jugador conectado ha ganado hasta ahora (de nuevo, no hay información guardada, solo cuántos para esta conexión) y proporcional a la dificultad del shuffle (hay un algoritmo que según los resultados de la mezcla puede determinar el grado de dificultad del juego).

¿Cómo se hace eso con un Object.create() estático Object.create() ?