Read our latest: The year so far & 1.0 update.
There are a lot of libraries out there to help with inheritance in JavaScript, and in truth, for simple single or prototype inheritance, Object.create goes a long way.
If you need more, many libraries include their own helper methods for Object Orientation. This is not really surprising as any large endevour is likely to start to want to talk in terms of the commonality shared by a class of objects which is exactly what a traditional OO class is, and relying only on the javascript built-ins for doing class inheritance involves writing some fairly opaque boilerplate.
So jQuery has a simple extend method, Backbone provides an extend method that properly sets up the prototype chain, prototype gives you a Class object that manages extension for you, underscore provides some basic extension methods too.
If you're uncomfortable including a full framework or general library for just the OO behaviour, there are also many more focussed microlibraries. ringjs is interesting because it implements Python style inheritance (including Python style multiple inheritance). There are a number of interesting libraries which add aspect oriented programming to the OO paradigm too, such as dcljs.
Even with a broad consensus on how Classes are implemented, the way the code the end user writes looks varies a great deal between libraries. We like our OO libraries to have a light touch - to remove some of the boilerplate, but not to lock you out of the normal JavaScript way of doing things.
One of the things we've found useful in building large systems that include code which needs to operate on objects provided by others is the concept of interfaces. While many libraries do inheritance well few of them provide some of the features we like around interfaces (qooxdoo is an exception, however it does lock you in to its way of defining classes).
In order to do interfaces well, we built our own library, topiarist. It also does inheritance, both single and multiple, and a sandboxed form of mixins which protect the state of the target class. It also supports more than one common way of writing code that uses it, from the 'traditional' to a more domain specific language style, similar to that favoured by libraries such as Backbone. I'll give examples of the different styles possible at the end of this article.
Javascript has support for single inheritance built into the language. The prototype chain is a chain of inheritance and you can query the chain with the instanceof
operator.
Topiarist's extend
method is an evolution of what I favoured back in early 2007 when I first started thinking about how to do class inheritance in javascript. It's also very similar to what most libraries (and languages that compile to JS such as TypeScript and CoffeeScript) have converged on.
topiarist.extend(subclass, superclass);
Even so, Topiarist does have some nice features over doing it yourself with Object.create
:
Most helpers for inheritance out there do something similar, which means that there's a good chance topiarist.extend
will interoperate with whatever your other favourite library is.
An interface is a description of a shape that an object can have.
Interfaces are a concept, so the fact that javascript doesn't have any native operators or keywords that support them doesn't mean that you don't have interfaces. All of your objects have a shape and sometimes it's useful for objects of different sorts to share the same shape so they can be treated in the same way.
Testing the shape of an object is often considered to be a 'non-javascripty' thing to do, but it can allow you to fail fast, and that can help you catch errors that you might otherwise have missed.
It's particularly useful to test for conformance to an interface when you're writing framework code that receives an object created by someone else and you want to be sure that the object you've been passed really does support all the relevant behaviour before continuing.
Topiarist's fulfills
method does a fairly standard duck type check. Given an object and another object describing the shape, it checks that the first object is the shape represented by the second.
topiarist.fulfills(instance, interface);
As well as taking an object to test, it can either take a function where the desired shape is that of its prototype, or a description of the shape in the form of a map of names to one of the type constructors (Number/Function/Object/String/Boolean).
In the following (much simplified) example, a variable is initialised in one method, and at some point later, two methods, foo
and numberOfFoos
are called on it. This is problematic, because the thisWillBeCalledLater
might not be called very often, and it could easily be missed in tests.
var obj = null;
function setFooable(passedFooable) {
obj = passedFooable;
}
function thisWillBeCalledLater() {
obj.foo();
return obj.numberOfFoos();
}
To make this fail fast, you could do the check inside setFooable
:
var obj = null;
function setFooable(passedFooable) {
if (!passedFooable.foo) {
throw new Error('passedFooable was not a real fooable');
}
if (!passedFooable.numberOfFoos) {
throw new Error('passedFooable was not a real fooable');
}
obj = passedFooable;
}
function thisWillBeCalledLater() {
obj.foo();
return obj.numberOfFoos();
}
This fails fast now, but it's getting ugly and the checking code might have to be repeated in numerous places. If what a fooable
is ever needed to change, the places you'd have to update in the code are scattered throughout.
toparist.fulfills
just refactors those checks into a single method, and provides a single definition of what a Fooable should look like.
var FooableProtocol = {
foo: Function,
numberOfFoos: Function
};
var obj = null;
function setFooable(passedFooable) {
if (! topiarist.fulfills(passedFooable, FooableProtocol)) {
throw new Error('passedFooable was not a real fooable');
}
obj = passedFooable;
}
function thisWillBeCalledLater() {
obj.foo();
return obj.numberOfFoos();
}
You can also define Fooable
more like a traditional javascript object definition, and then have JSdoc describing the contract associated with the interface since this style is more natural to document.
function Fooable() {}
/** jsdoc describing the contract of Fooable.foo */
Fooable.prototype.foo = function() {};
/** jsdoc describing the contract of Fooable.numberOfFoos */
Fooable.prottoype.numberOfFoos = function() {};
classFulfills
does the analogous operation to fulfills
, but testing a class instead of an instance object. The intention is that you can use this to see if the objects created by a particular constructor would match a particular shape. Obviously javascript is dynamic, so it's always possible that someone has deleted the property or changed its type. This is pretty rare so classFulfills
is still useful, particularly in situations where you've received some configuration telling you to construct a class and you'd like to check that the class is of the right shape before construction.
topiarist.classFulfills(class, interface);
Usually you're interested in the shape of objects that have been passed to you, but it's also useful to be able to quickly check that an object you're defining has the shape you are intending it to. That way you can get a fast failure with a useful message if you've missed something important rather than have to wait until some code actually tries to call it, which might happen for the first time when a customer is using the system rather than when you are.
Once you've defined a class, you can say topiarist.implements
to assert that the class you've defined fulfills a particular interface. If your class is missing anything, topiarist will throw an Error detailing the missing or incorrect properties.
topiarist.implements(class, interface);
The shape of an object does not entirely define its contract. For some objects the makeHistory
method might build a history of recent events, for others it might attempt to take some action of historical significance, and yet other objects might expect an argument for something to destroy.
Calling makeHistory
for something that had an entirely different understanding of what makeHistory
means just because it happens to have the shape you're expecting could be disastrous. That's why duck typing, which says that things of the same shape are the same, though usually right can go badly wrong.
Fortunately, we have a way of avoiding this kind of misunderstanding. If a developer has called topiarist.implements
to indicate that their object follows a particular interface, we can say that anything that implements that exact interface will have a consistent understanding of what the methods mean. So everything that implements BaseBallGame
will have one understanding of what the steal
method does, and that could well be different to things implementing CrownJewels
.
Just as fulfills
allows you to check the shape of an object, isA
allows you to query what has been declared about the object. It's therefore a way of finding out information that the developer has decided to publish about their semantic intentions for a piece of code.
isA
returns true for instances and any of their ancestors, whether from single inheritance, interfaces, or multiple or mixin inheritance. It will return false
if no parent-child relationship has been declared, even if the shapes are the same.
topiarist.isA(instance, parent);
The class equivalent is called classIsA
.
topiarist.classIsA(myClass, parent);
On the one hand, OO languages are intended to model real objects, and real objects are typically part of more than one conceptual hierarchy, on the other hand allowing inheritance from more than one parent opens up the question of what you should do if there are name clashes. If something is both a product and a missile, what does launch
do?
Different systems have different answers to that question. Some just say "well you're not allowed to do that", others just ignore the problem, while yet others accept the problem and just allow the developer to resolve it through specifying orders of parents. Java takes an approach which is a mix of the two - it disallows multiple inheritance of behaviour, regardless of whether there is a conflict or not and pretends (incorrectly) that the problem doesn't exist for interfaces.
public class Sidewinder implements Product, Missile {
@Override
public void launch() {
// Are there explosions or press-releases after calling this method?
}
}
Topiarist is more consistent; whenever it can detect a possible conflict of semantics it will disallow the inheritance, but otherwise it will allow multiple inheritance of either behaviour or contract.
In javascript, multiple inheritance is usually achieved by copying functionality over from the parent and topiarist does this too. It will however check to see if there is a semantic clash, and throw an error if there is. It also records the fact that your class has inherited from a parent in order to allow isA
queries to work against multiple inherited parents as well as interfaces and the single inheritance chain.
topiarist.inherit(class, parent);
In order to make clear what I mean by 'a semantic clash', let me give you an example:
Class Foo
has a method foo
. This means that the foo
action has a meaning in the context of things of type Foo
. If Bar
and Baz
both inherit from Foo
and neither overrides the foo
action, and then finally MyClass
multiple inherits from Bar
and Baz
, then there is no problem, because the foo
action on MyClass
has a single semantic meaning - it works by virtue of the fact that MyClass
is a Foo
, and the meaning of the foo
method is the Foo
meaning.
If Bar
were to override foo
, then there would still be no problem, because the foo
method MyClass
would inherit from Bar
would be appropriate for it being a Bar
and a Foo
, and since Baz
doesn't specify any behaviour different to that required by Foo
, it's appropriate for Baz
too.
If Bar
and Baz
were to provide implementations of foo
, then there would be a problem, because topiarist has no way of knowing if the Bar
concept of 'foo' is applicable to Baz'es or if the Baz
concept of 'foo' is applicable to Bars. Topiarist will detect this and throw an error if you try to inherit in this case.
Inheritance is almost always a little risky, since it usually involves some level of encapsulation-breaking, where the childs dependency on the parent is at a deeper level than the parents public API. This provides opportunities for the childs state to clash with the parents state.
One way of avoiding this is to receive behaviour (I use the term 'inherit', but not everyone agrees that is the correct usage) from a mixin. Mixins are intended as slices of functionality that can be added to a class, without any possibility of the state of the mixin clashing with the state of the receiving class.
Conceptually, inheriting from a mixin means adding the mixins behaviour to your class, whereas inheriting from a superclass means specialising the superclass.
This concept has been implemented in a number of different ways in different places. Topiarist considers a mixin to be a set of functionality that should not have access to the childs state. A classic example of a mixin is an observer, or in common javascript usage, an emitter.
function RadioShow() { }
topiarist.mixin(RadioShow, Emitter);
This will add definitions for .on
, .trigger
, etc to RadioShow
. The key difference between this and the topiarist.inherit
method described above is that these functions are sandboxed. If your emitter uses a this.listeners
array to store listeners added with .on
, and your RadioShow
class happens to use a property this.listeners
to store the number of listeners the programme receives, the Emitter
methods will continue to work. In fact, while the mixin has access to its own state per object, it has no access to the state of the instance. It can only call functions and affect state that it defines on the instance.
According to some people, it's not true to say in this case that RadioShow
is-a Emitter
because it mixes in the Emitter
functionality, rather than specialising the Emitter
concept, but since I can't imagine a situation where it would be useful for isA
to return false in this case, toparist takes the pragmatic view that adding a mixin causes you to become an example of the thing you mixed in for the purpose of isA
checks.
// works in node or the browser
var topiarist = (typeof require !== 'undefined' ? require('topiarist') : window['topiarist']);
function Furry() {}
Furry.prototype.stroke = function() {};
function Animal() {}
function Mammal() {
Animal.call(this); // remember to call superconstructors in your constructor.
};
topiarist.extend(Mammal, Animal);
topiarist.mixin(Mammal, Furry);
function Cat() {
Mammal.call(this);
};
topiarist.extend(Cat, Mammal);
var tabby = new Cat();
topiarist.isA(tabby, Cat); // true
topiarist.isA(tabby, Mammal); // true
topiarist.isA(tabby, Furry); // true
// there is also a topiarist.export, which will copy these methods to the global
// object if you want, so you can use isA/extend/mixin directly.
To use the DSL style, you can call install
which will add some nonenumerable extra methods to the Function
and Object
prototype. While this would be a questionable thing for a library to do automatically, it's a perfectly valid thing for an application to do.
topiarist.install();
function Mammal() {
Animal.call(this);
}
Mammal.extends(Animal);
Mammal.mixin(Furry);
function SomeInterface() {}
function Cat() {
Mammal.call(this);
}
Cat.extends(Mammal);
Cat.implements(SomeInterface);
var tabby = new Cat();
tabby.isA(Cat); // true
tabby.isA(Mammal); // true
If you want most of the benefits of calling topiarist.install
without altering builtins, you can instead extend the topiarist.Base
class to create your classes. This has the benefit that if you do not provide a constructor, it will automatically call the superconstructor. If you do provide a constructor, you should still call your superconstructor. This style is very similar to how you extend backbone classes.
// if you don't provide a constructor, the superconstructor will be automaticaly called.
var Animal = topiarist.Base.extend();
var Furry = topiarist.Base.extend({
stroke: function() {}
});
var Mammal = Animal.extend();
Mammal.mixin(Furry);
var Cat = Mammal.extend();
Topiarist is available to download from github and as topiarist
on npm or bower.