I’ve been working more in JavaScript and decided I need some inheritance. I had a couple of classes that need to inherit the properties and methods of a base class. I’ve learned inheritance methods in javascript. The prototype inheritance just never really felt right.
Well, today it just dawned on me that I might not so much want inheritance as I want one place to implement my fields and methods. The prototypal inheritance method doesn’t exactly work for what I want. I don’t want to add methods to the prototype.
See this project fully unit tested with qunit here: Simpler Javascript Inheritance
The inheritance template
To make this method of JavaScript inheritance work, all you have to do is define an objects members using a method. I will use an init method.
// The template
var TemplateBaseClass = function(child) {
var _self = this;
_self.init = function(obj) {
// define all members here using obj.member syntax.
};
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
};
With this template we can now implement inheritance with far more simplicity than the prototypal method.
Example base class and inheritance
Let’s look at an example BaseClass that uses the Inheritance template
// Non-abstract base class
// You can use new BaseClass() or any child.
var BaseClass = function(child) {
var _self = this;
_self.init = function(obj) {
obj.field1 = null;
obj.field2 = null;
obj.action1 = function() {
return true;
};
obj.action2 = function() {
return true;
};
};
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
};
The above BaseClass creates a few fields and methods. It uses the template we’ve created. It allows inheritance. Let’s inherit from it. Check out this ChildClass below.
// A child of BaseClass
var ChildClass = function(child) {
var _self = this;
var _base = new BaseClass(_self);
_self.init = function(obj) {
// Overriding a base method
obj.action1 = function() {
return _base.action1() && _self.truefalse; // can call base.action1()
};
// Child class fields and methods
obj.field5 = null;
obj.truefalse = false;
obj.action5 = function() {
return true;
};
// expose base class
obj.getBase = function() {
return _base;
}
}
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
}
As you see, ChildClass inherits BaseClass(). They have all the same field and method signatures. Methods can be defined in the base class and don’t have to be redefined. Also notice that we didn’t have to mess with the clunky object.prototype or object.Create() syntax.
Note: Well, this is sort of inheritance. BaseClass and ChildClass actually have completely separate instances of the fields and methods. We can fix this for methods (I’ll show you later) but we can’t really fix this for members. Still, that limitation doesn’t really cause any immediate problems.
Example abstract base class and inheritance
Well since we don’t really need the base class to actually have the fields and methods, let’s implement it so it doesn’t. Notice that we don’t call _self.init(_self)
.
// Abstract base class (like an Interfaces but with the implementation)
// While you can call new AbstractBaseClass(), it doesn't have any
// of its members.
var AbstractBaseClass = function(child) {
var _self = this;
_self.init = function(obj) {
obj.field3 = null;
obj.field4 = null;
obj.action3 = function() {
return true;
};
obj.action4 = function() {
return true;
};
};
if (child)
_self.init(child); // only instantiates fields and method to child
};
This is pretty identical to the code above, only the base class doesn’t implement any of the fields or methods it defines. I like to think of this as an Abstract class, or an interface that also includes implementation.
Let’s inherit from AbstractBaseClass.
// A child of the AbstractBaseClass
var ChildOfAbstractClass = function(child) {
var _self = this;
AbstractBaseClass(_self); // not saving AbstractBaseClass as it can't be used
_self.init = function(obj) {
// Overriding a base method
obj.action1 = function() {
// cannot call base.action1() because it doesn't exist
return obj.truefalse;
};
// Child class fields and methods
obj.field6 = null;
obj.truefalse = false;
obj.action6 = function() {
return true;
};
}
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
}
As you see, inheriting from an abstract class is the same with the exception that you cannot call any base members or methods as the base object doesn’t have them.
Implementation where base class and child class share fields and methods
Well, to share a method, the syntax is simple. Here is our original BaseClass from above, changed so methods in a child are the same as the method in the base class.
// Non-abstract base class
// You can use new BaseClass() or any child.
var BaseClass = function(child) {
var _self = this;
_self.init = function(obj) {
obj.field1 = null;
obj.field2 = null;
obj.action1 = _self.action1 || function() {
return true;
};
obj.action2 = _self.action1 || function() {
return true;
};
};
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
};
How simple was that. We just assign the object _self.action1 if it exists, if not we assign it a new function. This works because both the base class and child class have a reference to the same method. However, with fields, this wont’ work because the calling _self.field1 would return a value of null, not a reference. They will have the same value initially, but changing the value in the child class won’t change the value in the base class.
One nice way to fix this is to implement the Property object. This is nice as it is basically a wrapper around the ubiquitous get/set methods familiar to all languages. Here is a simple implementation of a Property.
// A property class
var Property = function() {
var _self = this;
var _backingField = null;
_self.get = function() {
return _backingField;
}
_self.set = function(value) {
_backingField = value;
}
}
Now let’s implement a base class using Properties instead of fields.
var BaseClassWithProperties = function(child) {
var _self = this;
_self.init = function(obj) {
obj.prop1 = _self.prop1 || new Property();
obj.prop2 = _self.prop1 || new Property();
};
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
};
As you see, instead of assigning the fields a null value, we assign them a new object. Notice we can use our syntax, _self.prop1 || new Property();
, to make sure that the base class and the child class share the same get/set Property.
Let’s implement a child class that also uses properties.
// A child of the BaseClassWithProperties
var ChildProperties = function(child) {
var _self = this;
var _base = new BaseClassWithProperties(_self); // not saving AbstractBaseClass as it can't be used
_self.init = function(obj) {
// Child class properties
obj.prop3 = new Property();
// expose base class
obj.getBase = function() {
return _base;
}
}
_self.init(_self); // instantiates fields and method to itself
if (child)
_self.init(child); // instantiates fields and method to child
}
Now if I create a new ChildProperties object and call it’s setter, the value is set for both the parent and the child object.
A sealed class
The idea of a sealed class is one that can’t be inherited from. Simply don’t implement the template and you have a sealed class.
// A child of the BaseClassWithProperties
// since it doesn't implement the child parameter and the init function, it is sealed and not usable for inheritance.
var SealedClass = function() {
var _self = this;
var _base = new BaseClassWithProperties(_self);
// Child class properties
_self.prop3 = new Property();
// expose base class
_self.getBase = function() {
return _base;
}
}
I want many child class that inherit the same