Reputation: 4141
I'm working on an ebook on GitHub on TDD JavaScript and I'm wondering if I'm missing any popular inheritance patterns. If you know of any additional patterns I'd love to see them. They should have the following:
The reason I'm doing this is that it seems that object inheritance in JavaScript has been quite difficult for many of us to grok. my JavaScript inheritance chapter is basically a study aid to: Crockford's Good Parts and Zakas's Professional JavaScript for Web Developers.
Here are the patterns I have so far:
// 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;
};
And for those interested, here are the jspec tests (sorry but Markdown or whatever they're using mangles the format a bit):
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
Thanks all! Oh, and if you want to check out my essay/book I'd love to get feedback: TDD JavaScript at GitHub repo
Upvotes: 16
Views: 11383
Reputation: 3413
Late to the party here but I have 2 points to make.
1) Please do not inform people to inherit through creating supertype objects. This is considered bad practice for a few reason. Firstly, its a principle mistake. You are instantiating objects just to use their methods and not doing anything with the instance per se. The right way to have done this is to use the Object.prototype.inherit method. In addition, this method forces you to leave the supertype constructor function argument empty, which may invoke an error under strict circumstances.
2) You forgot to mention the constructor stealing pattern.
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
//
}
Upvotes: 0
Reputation: 761
There's an interesting pattern worth mentioning here: a JavaScript constructor may return any object (not necesserily this). One could create a constructor function, that returns a proxy object, that contains proxy methods to the "real" methods of the "real" instance object. This may sound complicated, but it is not; here is a code snippet:
var MyClass = function() {
var instanceObj = this;
var proxyObj = {
myPublicMethod: function() {
return instanceObj.myPublicMethod.apply(instanceObj, arguments);
}
}
return proxyObj;
};
MyClass.prototype = {
_myPrivateMethod: function() {
...
},
myPublicMethod: function() {
...
}
};
The nice thing is that the proxy creation can be automated, if we define a convention for naming the protected methods. I created a little library that does exactly this: http://idya.github.com/oolib/
Upvotes: 1
Reputation: 92304
A co-worker at my previous company developed a library to do java like inheritance http://www.uselesspickles.com/class_library/. I think it's sexier than Rajendra's suggestions, syntax looks cleaner.
I wrote an article that demonstrates different ways to approach it, but making sure that the known bad practices are avoided. http://js-bits.blogspot.com/2010/08/javascript-inheritance-done-right.html, this is if you don't want to download a library but just want to copy paste some code that you can improve to do what you need.
Upvotes: 1
Reputation: 169693
I've got at least half a dozen implementations of various inheritance patterns lying around in my dev/web/stuff
folder, but they are mostly toys.
What I actually sometimes use is the following thin wrapper over JavaScript's default pseudo-class-based approach to make inheritance easier:
Function.prototype.derive = (function() {
function Dummy() {}
return function() {
Dummy.prototype = this.prototype;
return new Dummy;
};
})();
Example code:
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');
Another interesting one is the following, which automatically adds factory methods to a proper prototypal approach:
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;
};
});
Example code:
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';
You've already seen my implementation of classes.
Upvotes: 0
Reputation: 536587
See How to "properly" create a custom object in JavaScript? for a summary. (Might as well link it, since I wasted so much time typing it out!)
this:
Dog.prototype = new Animal();
would generally be avoided. You see it in example/tutorial code, but it's a horrible mess because it's basing a class on an instance, and an instance constructed in a faulty way: name
is undefined. Any more complicated constructor is going to get upset at that sort of thing.
Object.prototype.inherit=
Is a better approach for constructing, but prototyping anything into Object
is considered very poor taste. It runs the risk of messing up use of objects as trivial maps and breaking other code. You can put this helper function elsewhere, eg. Function.prototype.subclass
.
prototype.constructor
Personally I would tend to avoid, because constructor
has a special meaning in JavaScript (as implemented in Firefox and some other browsers; not IE's JScript), and that meaning is not what constructor
does here nor what you would expect any such property to do; it's confusing and almost always best avoided. So if including a link to the constructor function in the instance in a class system I would prefer to name it something else.
Upvotes: 8