reveal patterns pattern patrones example es6 diseño javascript design-patterns

javascript - patterns - Patrón OLOO de Kyle Simpson vs Patrón de diseño de prototipo



pattern javascript example (8)

¿El "Patrón OLOO (objetos vinculados a otros objetos)" de Kyle Simpson difiere de alguna manera del patrón de diseño del prototipo? Aparte de acuñarlo por algo que indica específicamente "vinculación" (el comportamiento de los prototipos) y aclarar que no hay "copia" que ocurra aquí (un comportamiento de clases), ¿qué introduce exactamente su patrón?

Aquí hay un ejemplo del patrón de Kyle de su libro, "You Don''t Know JS: this & Object Prototypes":

var Foo = { init: function(who) { this.me = who; }, identify: function() { return "I am " + this.me; } }; var Bar = Object.create(Foo); Bar.speak = function() { alert("Hello, " + this.identify() + "."); }; var b1 = Object.create(Bar); b1.init("b1"); var b2 = Object.create(Bar); b2.init("b2"); b1.speak(); // alerts: "Hello, I am b1." b2.speak(); // alerts: "Hello, I am b2."


"Pensé que hacerlo hacía que cada obj dependiera del otro"

Como explica Kyle cuando dos objetos están [[Prototype]] vinculados, no dependen realmente el uno del otro; en cambio son objeto individual. Estás vinculando un objeto al otro con un enlace [[Prototype]] que puedes cambiar en cualquier momento que desees. Si toma dos objetos vinculados [[Prototype]] creados a través del estilo OLOO como dependientes entre sí, también debe pensar lo mismo acerca de los creados a través de llamadas de constructor .

var foo= {}, bar= Object.create(foo), baz= Object.create(bar); console.log(Object.getPrototypeOf(foo)) //Object.prototype console.log(Object.getPrototypeOf(bar)) //foo console.log(Object.getPrototypeOf(baz)) //bar

Ahora piensa por un segundo, ¿crees que foo bar y baz son dependientes el uno del otro?

Ahora hagamos lo mismo este código de estilo de constructor

function Foo() {} function Bar() {} function Baz() {} Bar.prototype= Object.create(Foo); Baz.prototype= Object.create(Bar); var foo= new Foo(), bar= new Bar(). baz= new Baz(); console.log(Object.getPrototypeOf(foo)) //Foo.prototype console.log(Object.getPrototypeOf(Foo.prototype)) //Object.prototype console.log(Object.getPrototypeOf(bar)) //Bar.prototype console.log(Object.getPrototypeOf(Bar.prototype)) //Foo.prototype console.log(Object.getPrototypeOf(baz)) //Baz.prototype console.log(Object.getPrototypeOf(Baz.prototype)) //Bar.prototype

La única diferencia entre el último y el primer código es que en el último, los objetos foo , bar , baz están vinculados entre sí a través de objetos arbitrarios de su función de constructor ( Foo.prototype , Bar.prototype , Baz.prototype ) pero en el primero (estilo OLOO ) están vinculados directamente. En ambas formas, solo estás vinculando foo , bar , baz entre sí, directamente en el primero e indirectamente en el último. Pero, en ambos casos, los objetos son independientes entre sí porque no es realmente como una instancia de una clase que una vez instanciada, no se puede hacer que herede de otra clase. Siempre puede cambiar qué objeto debe delegar un objeto también.

var anotherObj= {}; Object.setPrototypeOf(foo, anotherObj);

Entonces, todos son independientes entre sí.

"Esperaba que OLOO resolviera el problema en el que cada objeto no sabe nada del otro".

Sí, eso es realmente posible

Usemos Tech como un objeto de utilidad.

var Tech= { tag: "technology", setName= function(name) { this.name= name; } }

cree tantos objetos como desee vinculados a Tech :

var html= Object.create(Tech), css= Object.create(Tech), js= Object.create(Tech); Some checking (avoiding console.log)- html.isPrototypeOf(css); //false html.isPrototypeOf(js); //false css.isPrototypeOf(html); //false css.isPrototypeOf(js); //false js.isPrototypeOf(html); //false js.isPrototypwOf(css); //false Tech.isPrototypeOf(html); //true Tech.isPrototypeOf(css); //true Tech.isPrototypeOf(js); //true

¿Crees que los objetos html , css , js están conectados entre sí? No, no lo son. Ahora veamos cómo podríamos haber hecho eso con la función de constructor .

function Tech() { } Tech.prototype.tag= "technology"; Tech.prototype.setName= function(name) { this.name= name; }

cree tantos objetos como desee vinculados al Tech.proptotype -

var html= new Tech(), css= new Tech(), js= new Tech();

Algunas comprobaciones (evitando console.log) -

html.isPrototypeOf(css); //false html.isPrototypeOf(js); //false css.isPrototypeOf(html); //false css.isPrototypeOf(js); //false js.isPrototypeOf(html); //false js.isPrototypeOf(css); //false Tech.prototype.isPrototypeOf(html); //true Tech.prototype.isPrototypeOf(css); //true Tech.prototype.isPrototypeOf(js); //true

¿Cómo crees que estos objetos de estilo constructor ( html , css , js ) difieren del código de estilo OLOO ? De hecho, tienen el mismo propósito. En el estilo OLOO uno delega objetos a Tech (la delegación se configuró explícitamente) mientras que en el estilo constructor uno delega a Tech.prototype (la delegación se configuró implícitamente). Finalmente, terminas vinculando los tres objetos, sin vinculación entre sí, a un objeto, directamente usando el estilo OLOO , indirectamente usando el estilo constructor .

"Como está, ObjB tiene que ser creado a partir de ObjA .. Object.create (ObjB), etc."

No, ObjB aquí no es como una instancia (en lenguajes clásicos) de ninguna clase ObjA . objB objeto objB se delega en el objeto ObjA en el momento de su creación " . Si utilizara el constructor, habría hecho el mismo ''acoplamiento'', aunque indirectamente haciendo uso de .prototype s.


¿Qué introduce exactamente su patrón?

OLOO adopta el prototipo de la cadena tal como está, sin necesidad de superponer otras semánticas (confusas de la OMI) para obtener el enlace.

Entonces, estos dos fragmentos tienen el mismo resultado EXACTO, pero llegan de manera diferente.

Forma de constructor:

function Foo() {} Foo.prototype.y = 11; function Bar() {} Bar.prototype = Object.create(Foo.prototype); Bar.prototype.z = 31; var x = new Bar(); x.y + x.z; // 42

Formulario OLOO:

var FooObj = { y: 11 }; var BarObj = Object.create(FooObj); BarObj.z = 31; var x = Object.create(BarObj); x.y + x.z; // 42

En ambos fragmentos, un objeto x está [[Prototype]] vinculado a un objeto ( Bar.prototype o BarObj ), que a su vez está vinculado a un tercer objeto ( Foo.prototype o FooObj ).

Las relaciones y la delegación son idénticas entre los fragmentos. El uso de la memoria es idéntico entre los fragmentos. La capacidad de crear muchos "hijos" (es decir, muchos objetos como x1 a x1000 , etc.) es idéntica entre los fragmentos. El rendimiento de la delegación ( xy y xz ) es idéntico entre los fragmentos. El rendimiento de creación de objetos es más lento con OLOO, pero la comprobación de la cordura que revela que el rendimiento más lento realmente no es un problema.

Lo que sostengo que OLOO ofrece es que es mucho más simple expresar los objetos y vincularlos directamente, que vincularlos indirectamente a través del constructor / new mecanismos. Este último pretende ser sobre clases, pero realmente es una sintaxis terrible para expresar delegación ( nota al margen: ¡también lo es class sintaxis de class ES6!).

OLOO solo está eliminando al intermediario.

Aquí hay otra comparación de class vs OLOO.


¿Hay alguna manera de OLOO más que "dos" objetos ... todos los ejemplos consisten en el ejemplo basado (ver el ejemplo de OP). Digamos que teníamos los siguientes objetos, ¿cómo podemos crear un "cuarto" objeto que tenga los atributos de los "otros" tres? ala ...

var Button = { init: function(name, cost) { this.buttonName = name; this.buttonCost = cost; } } var Shoe = { speed: 100 } var Bike = { range: ''4 miles'' }

Estos objetos son arbitrarios y pueden abarcar todo tipo de comportamientos. Pero lo esencial es que tenemos ''n'' número de objetos, y nuestro nuevo objeto necesita algo de los tres.

en lugar de los ejemplos dados ala:

var newObj = Object.create(oneSingularObject); newObj.whatever..

PERO, nuestro nuevo Objeto = (Botón, Bicicleta, Zapato) ......

¿Cuál es el patrón para que esto funcione en OLOO?


@Marcus @bholben

Quizás podamos hacer algo como esto.

const Point = { statics(m) { if (this !== Point) { throw Error(m); }}, create (x, y) { this.statics(); var P = Object.create(Point); P.init(x, y); return P; }, init(x=0, y=0) { this.x = x; this.y = y; } }; const Point3D = { __proto__: Point, statics(m) { if (this !== Point3D) { throw Error(m); }}, create (x, y, z) { this.statics(); var P = Object.create(Point3D); P.init(x, y, z); return P; }, init (x=0, y=0, z=0) { super.init(x, y); this.z = z; } };

Por supuesto, crear un objeto Point3D que se vincule al prototipo de un objeto Point2D es un poco tonto, pero eso no viene al caso (quería ser coherente con su ejemplo). De todos modos, en lo que respecta a las quejas:

  1. La asimetría se puede arreglar con Object.setPrototypeOf de ES6 o con el más mal visto __proto__ = ... que uso. Ahora también podemos usar super en objetos normales, como se ve en Point3D.init() . Otra forma sería hacer algo como

    const Point3D = Object.assign(Object.create(Point), { ... }

    aunque no me gusta particularmente la sintaxis.

  1. Siempre podemos envolver p = Object.create(Point) y luego p.init() en un constructor. por ejemplo, Point.create(x,y) . Usando el código anterior, podemos crear una "instancia" de Point3D de la siguiente manera.

    var b = Point3D.create(1,2,3); console.log(b); // { x:1, y:2, z:3 } console.log(Point.isPrototypeOf(b)); // true console.log(Point3D.isPrototypeOf(b)) // true

  1. Se me ocurrió este truco para emular métodos estáticos en OLOO. No estoy seguro de si me gusta o no. Requiere llamar a una propiedad especial en la parte superior de cualquier método "estático". Por ejemplo, hice que el método Point.create() estático.

    var p = Point.create(1,2); var q = p.create(4,1); // Error!

Alternativamente, con los Symbols ES6 puede ampliar de forma segura las clases base de Javascript. Por lo tanto, podría ahorrarse algo de código y definir la propiedad especial en Object.prototype. Por ejemplo,

const extendedJS = {}; ( function(extension) { const statics = Symbol(''static''); Object.defineProperty(Object.prototype, statics, { writable: true, enumerable: false, configurable: true, value(obj, message) { if (this !== obj) throw Error(message); } }); Object.assign(extension, {statics}); })(extendedJS); const Point = { create (x, y) { this[extendedJS.statics](Point); ...


@Marcus, al igual que tú, he estado interesado en OLOO y también me disgusta la asimetría como se describe en tu primer punto. He estado jugando con una abstracción para recuperar la simetría. Puede crear una función link() que se use en lugar de Object.create() . Cuando se usa, su código podría verse así ...

var Point = { init : function(x,y) { this.x = x; this.y = y; } }; var Point3D = link(Point, { init: function(x,y,z) { Point.init.call(this, x, y); this.z = z; } });

Recuerde que Object.create() tiene un segundo parámetro que se puede pasar. Aquí está la función de enlace que aprovecha el segundo parámetro. También permite un poco de configuración personalizada ...

function link(delegate, props, propsConfig) { props = props || {}; propsConfig = propsConfig || {}; var obj = {}; Object.keys(props).forEach(function (key) { obj[key] = { value: props[key], enumerable: propsConfig.isEnumerable || true, writable: propsConfig.isWritable || true, configurable: propsConfig.isConfigurable || true }; }); return Object.create(delegate, obj); }

Por supuesto, creo que @Kyle no respaldaría sombrear la función init() en el objeto Point3D. ;-)


@james emanon - Entonces, te estás refiriendo a la herencia múltiple (discutido en la página 75 del libro "No sabes JS: esto y prototipos de objetos"). Y ese mecanismo que podemos encontrar en la función "extender" de subrayado, por ejemplo. Los nombres de objetos que indicó en su ejemplo son un poco mezclando manzanas, naranjas y dulces, pero entiendo el punto detrás. Desde mi experiencia, esta sería la versión OOLO:

var ObjA = { setA: function(a) { this.a = a; }, outputA: function() { console.log("Invoking outputA - A: ", this.a); } }; // ''ObjB'' links/delegates to ''ObjA'' var ObjB = Object.create( ObjA ); ObjB.setB = function(b) { this.b = b; } ObjB.setA_B = function(a, b) { this.setA( a ); // This is obvious. ''setA'' is not found in ''ObjB'' so by prototype chain it''s found in ''ObjA'' this.setB( b ); console.log("Invoking setA_B - A: ", this.a, " B: ", this.b); }; // ''ObjC'' links/delegates to ''ObjB'' var ObjC = Object.create( ObjB ); ObjC.setC = function(c) { this.c = c; }; ObjC.setA_C = function(a, c) { this.setA( a ); // Invoking ''setA'' that is clearly not in ObjC shows that prototype chaining goes through ObjB all the way to the ObjA this.setC( c ); console.log("Invoking setA_C - A: ", this.a, " C: ", this.c); }; ObjC.setA_B_C = function(a, b, c){ this.setA( a ); // Invoking ''setA'' that is clearly not in ObjC nor ObjB shows that prototype chaining got all the way to the ObjA this.setB( b ); this.setC( c ); console.log("Invoking setA_B_C - A: ", this.a, " B: ", this.b, " C: ", this.c); }; ObjA.setA("A1"); ObjA.outputA(); // Invoking outputA - A: A1 ObjB.setA_B("A2", "B1"); // Invoking setA_B - A: A2 B: B1 ObjC.setA_C("A3", "C1"); // Invoking setA_C - A: A3 C: C1 ObjC.setA_B_C("A4", "B2", "C1"); // Invoking setA_B_C - A: A4 B: B2 C: C1

Es un ejemplo simple, pero el punto que se muestra es que solo estamos encadenando objetos en una estructura / formación bastante plana y aún tenemos la posibilidad de usar métodos y propiedades de múltiples objetos. Logramos lo mismo que con el enfoque de clase / "copiar las propiedades". Resumido por Kyle (página 114, "this & Object Prototypes"):

En otras palabras, el mecanismo real, la esencia de lo que es importante para la funcionalidad que podemos aprovechar en JavaScript, se trata de que los objetos estén vinculados a otros objetos .

Entiendo que una forma más natural para usted sería establecer todos los objetos "padres" (cuidadosos :)) en un lugar / llamada de función en lugar de modelar toda la cadena.

Lo que requiere es un cambio en los problemas de pensamiento y modelado en nuestras aplicaciones de acuerdo con eso. También me estoy acostumbrando. Espero que ayude y el veredicto final del propio Kyle sería genial. :)


La discusión en "You Don''t Know JS: this & Object Prototypes" y la presentación del OLOO son estimulantes y he aprendido muchísimo a través del libro. Los méritos del patrón OLOO están bien descritos en las otras respuestas; sin embargo, tengo las siguientes quejas de mascotas (o me falta algo que me impide aplicarlo de manera efectiva):

1

Cuando una "clase" "hereda" otra "clase" en el patrón clásico, las dos funciones pueden declararse sintaxis similar ( "declaración de función" o "declaración de función" ):

function Point(x,y) { this.x = x; this.y = y; }; function Point3D(x,y,z) { Point.call(this, x,y); this.z = z; }; Point3D.prototype = Object.create(Point.prototype);

Por el contrario, en el patrón OLOO, se utilizan diferentes formas sintácticas para definir la base y los objetos derivados:

var Point = { init : function(x,y) { this.x = x; this.y = y; } }; var Point3D = Object.create(Point); Point3D.init = function(x,y,z) { Point.init.call(this, x, y); this.z = z; };

Como puede ver en el ejemplo anterior, el objeto base se puede definir usando la notación literal del objeto, mientras que la misma notación no se puede usar para el objeto derivado. Esta asimetría me molesta.

2

En el patrón OLOO, crear un objeto es dos pasos:

  1. llamar a Object.create
  2. llame a un método personalizado no estándar para inicializar el objeto (que debe recordar ya que puede variar de un objeto a otro):

    var p2a = Object.create(Point); p2a.init(1,1);

Por el contrario, en el patrón Prototipo, utiliza el operador estándar new :

var p2a = new Point(1,1);

3

En el patrón clásico, puedo crear funciones de utilidad "estáticas" que no se aplican directamente a un "instante" asignándolas directamente a la función de "clase" (a diferencia de su .prototype ). Por ejemplo, como función square en el siguiente código:

Point.square = function(x) {return x*x;}; Point.prototype.length = function() { return Math.sqrt(Point.square(this.x)+Point.square(this.y)); };

Por el contrario, en el patrón OLOO también hay disponibles funciones "estáticas" (a través de la cadena [[prototype]]) en las instancias de objetos:

var Point = { init : function(x,y) { this.x = x; this.y = y; }, square: function(x) {return x*x;}, length: function() {return Math.sqrt(Point.square(this.x)+Point.square(this.y));} };


Leí el libro de Kyle, y lo encontré realmente informativo, particularmente los detalles acerca de cómo está vinculado.

Pros:

Para mí, hay un par de grandes profesionales de OLOO:

1. simplicidad

OLOO se basa en Object.create() para crear un nuevo objeto que está [[prototype]] vinculado a otro objeto. No tiene que comprender que las funciones tienen una propiedad prototype ni preocuparse por ninguno de los posibles problemas relacionados que surgen de su modificación.

2. Sintaxis más limpia

Esto es discutible, pero creo que la sintaxis de OLOO es (en muchos casos) más ordenada y más concisa que el enfoque de JavaScript ''estándar'', particularmente cuando se trata de polimorfismo (llamadas de super estilo).

Contras:

Creo que hay un diseño cuestionable (uno que realmente contribuye al punto 2 anterior), y que tiene que ver con el sombreado:

En la delegación de comportamiento, evitamos si es posible nombrar cosas iguales en diferentes niveles de la cadena [[Prototype]] .

La idea detrás de esto es que los objetos tienen sus propias funciones más específicas que luego delegan internamente en funciones más abajo en la cadena. Por ejemplo, puede tener un objeto de resource con una función save() que envía una versión JSON del objeto al servidor, pero también puede tener un objeto clientResource que tiene una función stripAndSave() , que primero elimina las propiedades que No debe enviarse al servidor.

El problema potencial es: si alguien más aparece y decide hacer un objeto especial de specialResource , que no es completamente consciente de toda la cadena de prototipos, podría razonablemente * decidir guardar una marca de tiempo para el último guardado bajo una propiedad llamada save , que sombrea la base save() en el objeto de resource dos enlaces en la cadena del prototipo:

var resource = { save: function () { console.log(''Saving''); } }; var clientResource = Object.create(resource); clientResource.stripAndSave = function () { // Do something else, then delegate console.log(''Stripping unwanted properties''); this.save(); }; var specialResource = Object.create( clientResource ); specialResource.timeStampedSave = function () { // Set the timestamp of the last save this.save = Date.now(); this.stripAndSave(); }; a = Object.create(clientResource); b = Object.create(specialResource); a.stripAndSave(); // "Stripping unwanted properties" & "Saving". b.timeStampedSave(); // Error!

Este es un ejemplo particularmente artificial, pero el punto es que específicamente no ocultar otras propiedades puede conducir a situaciones incómodas y al uso intensivo de un tesauro.

Quizás una mejor ilustración de esto sería un método init , particularmente conmovedor, ya que OOLO evita las funciones de tipo constructor. Dado que cada objeto relacionado probablemente necesitará dicha función, puede ser un ejercicio tedioso nombrarlos apropiadamente, y la singularidad puede hacer que sea difícil recordar cuál usar.

* En realidad no es particularmente razonable ( lastSaved sería mucho mejor, pero es solo un ejemplo).