2009-10-29 20 views
16

Estoy trabajando en un ebook on GitHub en JavaScript TDD y me pregunto si me faltan patrones de herencia populares. Si conoces algún patrón adicional, me encantaría verlos. Se debe tener lo siguiente:Patrones de herencia de JavaScript populares

  1. Tiempo probado - utilizado en aplicaciones reales
  2. El código fuente debe ser suministrado. Debería ser lo más directo y pedante posible.
  3. Por supuesto, es correcto y funciona.

La razón por la que estoy haciendo esto es porque parece que la herencia de objetos en JavaScript ha sido bastante difícil de asimilar para muchos de nosotros. Mi capítulo de herencia de JavaScript es básicamente una ayuda de estudio para: Good Parts de Crockford y JavaScript profesional de Zakas para desarrolladores web.

Éstos son los patrones que tengo hasta ahora:

// Pseudoclassical Inheritance 
    function Animal(name) { 
     this.name = name; 
     this.arr = [1,2,3]; 
    }; 
    Animal.prototype = { 
     constructor: Animal, 
     whoAmI: function() { return "I am " + this.name + "!\n"; } 
    }; 

    function Dog(name, breed) { 
     this.name = name; 
     this.breed = breed; 
    }; 
    Dog.prototype = new Animal(); 
    Dog.prototype.getBreed = function() { 
     return this.breed; 
    }; 
    Dog.prototype.bark = function() { 
     return 'ruff ruff'; 
    }; 

    // Combination Inheritance 
    function Parent(name) { 
     this.name = name; 
     this.arr = [1,2,3]; 
    }; 
    Parent.prototype = { 
     constructor: Parent, 
     toString: function() { return "My name is " + this.name; } 
    }; 
    function Child(name, age) { 
     Parent.call(this, name); 
     this.age = age; 
    }; 

    Child.prototype = new Parent(); 

    Child.prototype.getAge = function() { 
     return this.age; 
    }; 

    // Prototypal Inheritance 
    var helper = { // Thanks to Bob Vince for reminding me NOT to clobber Object! 

     inherit: function(p) { 
     NewObj = function(){}; 
     NewObj.prototype = p; 
     return new NewObj(); 
     }, 
     inheritPrototype: function(subType, superType) { 
     var prototype = helper.inherit(superType.prototype); 
     prototype.constructor = subType; 
     subType.prototype = prototype; 
     } 
    }; 

    function SubType(name, age) { 
     Parent.call(this, name); 
     this.age = age;  
    }; 
    //Child.prototype = new Parent(); // Gets replaced by: 
    helper.inheritPrototype(SubType, Parent); 
    SubType.prototype.getAge = function() { 
     return this.age; 
    }; 

    // Functional - Durable Pattern 
    function super_func(blueprint) { 
     var obj = {}; 
     obj.getName = function() { return blueprint.name; }; 
     obj.getAge = function() { return blueprint.age; }; 
     obj.getFoo = function() { return blueprint.foo; }; 
     obj.getBar = function() { return blueprint.bar; }; 
     return obj; 
    }; 
    function sub_func(blueprint) { 
     blueprint.name = blueprint.name || "Crockford's Place"; 
     supr = super_func(blueprint); 
     supr.coolAugment = function() { return "I give a fresh new perspective on things!" }; 
     return supr;  
    }; 

Y para los interesados, aquí están las pruebas JSpec (lo siento, pero de rebajas o lo que sea que están usando el formato de mangles un poco):

describe 'JavaScript Inheritance Tests' 
    before_each 
    animal = new Animal("Onyx") 
    dog = new Dog("Sebastian", "Lab") 

    person = { password : 'secret', toString : function(){ return '<Person>' } } 
    stub(person, 'toString').and_return('Original toString method!')  
    end 
    describe 'Pseudoclassical Inheritance Creation' 
    it 'should create parent and child object using pseudoclassical inheritance' 
     animal.constructor.should.eql Animal 
     // dog.constructor.should.eql Dog // Nope: expected Animal to eql Dog 
     dog.constructor.should.eql Animal 
     animal.should.be_a Animal 
     dog.should.be_a Animal 
     // dog.should.be_a Dog // Nope! We severed the original prototype pointer and now point to Animal! 
     dog.should.be_an_instance_of Animal 
     dog.should.be_an_instance_of Dog 
     (animal instanceof Dog).should.be_false 
    end 
    it 'should behave such that child inherits methods and instance variables defined in parent' 
     animal.whoAmI().should.match /I am Onyx.*/ 
     dog.whoAmI().should.match /Sebastian.*/ 
     animal.should.respond_to 'whoAmI' 
     dog.should.respond_to 'whoAmI' 
     dog.should.have_prop 'name' 
    end 
    it 'should behave such that methods and instance variables added to child are NOT available to parent' 
     dog.bark().should.match /Ruff Ruff/i 
     dog.should.have_property 'breed' 
     dog.should.respond_to 'bark' 
     // animal.should.have_prop 'breed' // Of course not! 
     // animal.should.respond_to 'bark' // Of course not! 
    end 
    it 'should behave such that reference variables on the parent are "staticy" to all child instances' 
     dog.arr.should.eql([1,2,3]) 
     dog.arr.push(4) 
     dog.arr.should.eql([1,2,3,4]) 
     spike = new Dog("Spike", "Pitbull") 
     spike.arr.should.eql([1,2,3,4]) 
     spike.arr.push(5) 
     rover = new Dog("Rover", "German Sheppard") 
     spike.arr.should.eql([1,2,3,4,5]) 
     rover.arr.should.eql([1,2,3,4,5]) 
     dog.arr.should.eql([1,2,3,4,5]) 
    end 
    end 

    describe 'Combination Inheritance Solves Static Prototype Properties Issue' 
    it 'should maintain separate state for each child object' 
     child_1 = new Child("David", 21) 
     child_2 = new Child("Peter", 32) 
     child_1.arr.push(999) 
     child_2.arr.push(333) 
     child_1.arr.should.eql([1,2,3,999]) 
     child_2.arr.should.eql([1,2,3,333]) 
     child_1.getAge().should.eql 21 
     child_1.should.be_a Parent 
    end 
    end 

    describe 'Prototypal Inheritance' 
    it 'should inherit properties from parent' 
     person.toString().should.match /Original toString.*/i 
     person.password.should.eql 'secret' 
     joe = helper.inherit(person) 
     joe.password.should.eql 'secret' 
     joe.password = 'letmein' 
     joe.password.should.eql 'letmein' 
     person.password.should.eql 'secret' 
    end 
    end 

    describe 'Parisitic Combination Inheritance' 
    it 'should use inheritPrototype (to call parent constructor once) and still work as expected' 
     sub = new SubType("Nicholas Zakas", 29) 
     sub.toString().should.match /.*Nicholas Zakas/ 
     sub.getAge().should.eql 29 
     charlie = new SubType("Charlie Brown", 69) 
     charlie.arr.should.eql([1,2,3]) 
     charlie.arr.push(999) 
     charlie.arr.should.eql([1,2,3,999]) 
     sub.arr.should.eql([1,2,3]) 
     sub.should.be_an_instance_of SubType 
     charlie.should.be_an_instance_of SubType 
     (sub instanceof SubType).should.eql true 
     (sub instanceof Parent).should.eql true 
    end 
    end 

    describe 'Functional Durable Inheritance' 
    it 'should hide private variables' 
     sup = new super_func({name: "Superfly Douglas", age: 39, foo: "foo", bar: "bar"}) 
     sup.getName().should.eql 'Superfly Douglas' 
     sup.name.should.be_undefined 
     sup.getAge().should.eql 39 
     sup.age.should.be_undefined 
     sup.getFoo().should.eql 'foo' 
     sup.foo.should.be_undefined 
    end 

    it 'should create a descendent object that inherits properties while maintaining privacy' 
     sub = new sub_func({name: "Submarine", age: 1, foo: "food", bar: "barfly"}) 
     sub.getName().should.eql 'Submarine' 
     sub.name.should.be_undefined 
     sub.getAge().should.eql 1 
     sub.age.should.be_undefined 
     sub.getFoo().should.eql 'food' 
     sub.foo.should.be_undefined 
     sub.getBar().should.eql 'barfly' 
     sub.bar.should.be_undefined 
     sub.coolAugment().should.match /.*fresh new perspective.*/ 
     //sub.should.be_an_instance_of super_func NOPE! 
     //sub.should.be_an_instance_of sub_func NOPE! 
     sub.should.be_an_instance_of Object 
    end 
    end 

end 

¡Gracias a todos! Ah, y si quieres ver mi ensayo/libro, me gustaría recibir comentarios: TDD JavaScript at GitHub repo

+0

Probé muchas técnicas en muchos proyectos diferentes. Mi favorito es usar Object.create() para crear una instancia del prototipo base. Compruebe mi blog: http://ncombo.wordpress.com/2013/07/11/javascript-inheritance-done-right/ – Jon

Respuesta

8

Ver How to "properly" create a custom object in JavaScript? para un resumen. (La fuerza también vincularlo, puesto que perdido tanto tiempo escribiendo un vistazo!)

esto:

Dog.prototype = new Animal();

generalmente se evitarán. Lo ves en el ejemplo/código de tutorial, pero es un lío horrible porque basa una clase en una instancia y una instancia construida de forma incorrecta: name no está definido. Cualquier constructor más complicado se va a enojar por ese tipo de cosas.

Object.prototype.inherit =

es un mejor enfoque para la construcción, pero nada en la creación de prototipos Object se considera de muy mal gusto. Se corre el riesgo de arruinar el uso de objetos como mapas triviales y romper otro código. Puede poner esta función auxiliar en otro lugar, por ej. Function.prototype.subclass.

prototype.constructor

Personalmente yo tendería a evitar, porque constructor tiene un significado especial en JavaScript (tal como se aplica en Firefox y otros navegadores, no de IE JScript), y que el significado no es lo que constructor hace aquí ni lo que esperaría que hiciera ninguna de esas propiedades; es confuso y casi siempre es mejor evitarlo. Entonces, si incluyo un enlace a la función de constructor en la instancia en un sistema de clase, preferiría nombrarlo de otra manera.

+0

Gracias por sus comentarios. Estoy de acuerdo con Object.prototype.inherit y creo que mencioné esto en mi ensayo, pero al menos lo añadiré a los comentarios sobre el código y/o lo pondré en una función de contenedor. Gracias por atrapar eso! Dog.prototype = new Animal() Doy alternativas en patrones posteriores. Gracias por tu entrada, Bob. – Rob

+0

Seguí adelante y actualicé el código (y edité el listado de códigos anterior) para usar un objeto auxiliar en lugar de un objeto obstinado. – Rob

+0

Sí, volví y leí mi ensayo y dije: "Por supuesto, no hay nada que le exija un parche de mono Objeto: podría crear un envoltorio que devuelva dicha funcionalidad si lo desea". Simplemente perezoso! ¡Gracias por hacerme "hacerlo bien"! – Rob

0

Tengo al menos media docena de implementaciones de varios patrones de herencia en mi carpeta dev/web/stuff, pero en su mayoría son juguetes.

Lo que realmente utilizo a veces es la siguiente envoltura fina sobre enfoque basado en la pseudo-clase por defecto de JavaScript para hacer que la herencia más fácil:

Function.prototype.derive = (function() { 
    function Dummy() {} 
    return function() { 
     Dummy.prototype = this.prototype; 
     return new Dummy; 
    }; 
})(); 

código Ejemplo:

function Pet(owner, type, name) { 
    this.owner = owner; 
    this.type = type; 
    this.name = name; 
} 

Pet.prototype.toString = function() { 
    return this.owner + '\'s ' + this.type + ' ' + this.name; 
}; 

function Cat(owner, name) { 
    Pet.call(this, owner, 'cat', name); 
} 

Cat.prototype = Pet.derive(); 

var souris = new Cat('Christoph', 'Souris'); 

Otro interesante es el siguiente, que agrega automáticamente los métodos de fábrica a un enfoque prototípico adecuado:

var Proto = new (function() { 
    function Dummy() {} 

    this.clone = function() { 
     Dummy.prototype = this; 
     return new Dummy; 
    }; 

    this.init = function() {}; 

    this.create = function() { 
     var obj = this.clone(); 
     this.init.apply(obj, arguments); 
     return obj; 
    }; 
}); 
código

Ejemplo:

var Pet = Proto.clone(); 

Pet.init = function(owner, type, name) { 
    this.owner = owner; 
    this.type = type; 
    this.name = name; 
}; 

Pet.toString = function() { 
    return this.owner + '\'s ' + this.type + ' ' + this.name; 
}; 

Cat = Pet.clone(); 

Cat.init = function(owner, name) { 
    Pet.init.call(this, owner, 'cat', name); 
}; 

// use factory method 
var filou = Cat.create('Christoph', 'Filou'); 

// use cloning (the proper prototypal approach) 
var red = filou.clone(); 
red.name = 'Red'; 

Tienes already seen mi implementation of classes.

+0

¡Hola Christoph! Gracias. Entonces, su método de derivación es realmente el mismo que mi método heredado, excepto que devuelve un cierre y se llama a sí mismo; el patrón también usa robo de constructor, creo que es esencialmente un patrón de herencia de combinación (combinación de prototipo y robo de constructor). Voy a tener que mirar un poco más el otro patrón, creo que jugaré con él esta noche e informaré ;-) Gracias por compartir a Christoph. – Rob

1

Un compañero de trabajo en mi empresa anterior desarrolló una biblioteca para hacer una herencia java como http://www.uselesspickles.com/class_library/. Creo que es más sexy que las sugerencias de Rajendra, la sintaxis se ve más limpia.

Escribí un artículo que demuestra diferentes formas de abordarlo, pero asegurándome de que se eviten las malas prácticas conocidas. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html, esto es si no desea descargar una biblioteca pero solo quiere copiar y pegar un código que puede mejorar para hacer lo que necesita.

1

Hay un patrón interesante que vale la pena mencionar aquí: un constructor JavaScript puede devolver cualquier objeto (no necesariamente este). Se podría crear una función de constructor, que devuelve un objeto proxy, que contiene métodos proxy para los métodos "reales" del objeto de instancia "real". Esto puede sonar complicado, pero no lo es; aquí es un fragmento de código:

var MyClass = function() { 
    var instanceObj = this; 
    var proxyObj = { 
     myPublicMethod: function() { 
      return instanceObj.myPublicMethod.apply(instanceObj, arguments); 
     } 
    } 
    return proxyObj; 
}; 
MyClass.prototype = { 
    _myPrivateMethod: function() { 
     ... 
    }, 
    myPublicMethod: function() { 
     ... 
    } 
}; 

Lo bueno es que la creación de proxy puede ser automatizado, si definimos una convención para nombrar los métodos protegidos. Creé una pequeña biblioteca que hace exactamente esto: http://idya.github.com/oolib/

0

Tarde para la fiesta aquí pero tengo 2 puntos para hacer.

1) No informe a las personas a heredar creando objetos de supertipo. Esto se considera una mala práctica por algunas razones. En primer lugar, es un error principal. Estás instanciando objetos solo para usar sus métodos y no hacer nada con la instancia per se. La forma correcta de hacerlo es usar el método Object.prototype.inherit. Además, este método le obliga a dejar el argumento de la función del constructor supertipo vacío, lo que puede provocar un error en circunstancias estrictas.

2) Olvidó mencionar el patrón de robo del constructor.

function Supertype(name){ 
this.name=name; 
this.sayName = function(){console.log(this.name);}; 
} 

function Subtype(name){ 
//inherit by using (stealing) supertype constructor function 
Supertype(name); 

// child specific properties 
// 
}