Object-oriented JavaScript
JavaScript is a prototype-based object-oriented language. You can use Caplin Trader 4’s Topiarist library to model classes, interfaces, and inheritence in JavaScript. You should use Topiarist in preference to the Bootstrap.js methods caplin.extend
and caplin.implement
, the use of which is now deprecated. The caplin.extend
and caplin.implement
methods will be removed in a future major release of Caplin Trader.
Topiarist library
Topiarist is the recommend library for writing object-oriented JavaScript in Caplin Trader 4.
For an overview of Topiarist, see the pages below:
Bootstrap.js methods (deprecated)
This section provides an overview of writing object-oriented JavaScript using the deprecated methods caplin.extend
and caplin.implement
.
Declaring interfaces
Declaring an interface is essentially the same as declaring a class. With interfaces though, it’s important to provide feedback when an arbitrary method has not been implemented. You can do that by using br.util.Utility.interfaceMethod()
, within the body of each of your interface’s methods.
Using Animal
as an example interface, your definition could look something like this:
var brUtility = require('br/util/Utility');
function Animal() {
}
Animal.prototype.speak = function() {
brUtility.interfaceMethod('novox.Animal' , 'speak');
};
Animal.prototype.getGender = function() {
brUtility.interfaceMethod('novox.Animal' , 'getGender');
};
Animal.prototype.getLegCount = function() {
brUtility.interfaceMethod('novox.Animal' , 'getLegCount');
};
module.exports = Animal;
If another class implements the Animal
interface, it must implement all its methods as well, or it will not be able to invoke them. For example, if a class implements Animal
, but does not provide an implementation for the method speak()
, any invocation to speak
will throw an exception with the message "Unexpected exception (This is an interface method (novox.Animal.speak))".
Implementing interfaces
You can implement an interface using the caplin.implement
method. You can use caplin.implement
to implement as many interfaces as you wish.
If we wanted to declare a Quadruped
class that implements the Animal
interface, we could do it like this:
var Animal = require('novox/Animal');
function Quadruped(sGender) {
this.m_sGender = sGender;
};
caplin.implement(Quadruped, Animal);
Quadruped.prototype.getLegCount = function() {
return 4;
};
Quadruped.prototype.getGender = function() {
return this.m_sGender;
};
module.exports = Quadruped;
Note that it’s important to implement the interface before any method declarations for it to work properly. It’s also important to provide the actual JavaScript constructor functions to caplin.implement
rather than the function names as strings. You may also notice the Quadruped
class hasn’t implemented the speak()
method, which effectively makes this an abstract class.
Extending classes
You can extend a class using the caplin.extend()
method. You should declare the class extension before you declare any of its methods.
As Quadruped
is an abstract class, we are going to need to extend it to provide a concrete implementation. Let’s extend Quadruped
to give us two concrete classes of Quadruped
: Cat
and Dog
.
The Cat
class:
var Quadruped = require('novox/Quadruped');
function Cat(sGender) {
};
caplin.extend(Cat, Quadruped);
Cat.prototype.speak = function() {
alert('Meow!');
}
module.exports = Cat;
The Dog
class:
var Quadruped = require('novox/Quadruped');
function Dog(sGender) {
}
caplin.extend(Dog, Quadruped);
Dog.prototype.speak = function() {
alert('Woof!');
};
module.exports = Dog;
By extending the Quadruped
class and implementing the remaining method from the Animal
interface, each of these new classes is complete and we could create instances of them.
Calling super constructors
The JavaScript method apply
allows us to call a super constructor while retaining the correct this
pointer.
We can call the super Quadruped
constructor from within the Dog
and Cat
constructors by calling Quadruped.apply(this, arguments)
.
The rewritten Dog
constructor:
function Dog(sGender) {
Quadruped.apply(this, arguments);
};
The rewritten Cat
constructor:
function Cat(sGender) {
Quadruped.apply(this, arguments);
};
If we added any additional arguments at a later date, using the arguments
keyword ensures that they would all be passed back to the Quadruped
super-constructor without having to explicitly add them to the apply()
method in each constructor.
Overriding super methods
The JavaScript method call
allows us to call super methods while retaining the correct this
pointer.
By default, our cats and dogs all have four legs, a feature that they inherit from the Quadruped
class. If we wanted to allow for the occasional three-legged dog we might encounter, the following code would allow us to override Quadruped.getLegCount
for three-legged dogs, but still call Quadruped.getLegCount
for four-legged dogs:
Dog.prototype.getLegCount = function() {
if (this.is3LeggedDog()) {
return 3;
}
return Quadruped.prototype.getLegCount.call(this);
};
Writing static classes and enums
It may be useful to provide static classes and enums to complement particular classes and interfaces. You can write static classes and enums by adding properties and methods to a constructor function rather than to the constructor function’s prototype.
For example, we may want to provide an AnimalGender
enum to be used in conjunction with the Animal
interface:
function AnimalGender() {
}
AnimalGender.MALE = 'male';
AnimalGender.FEMALE = 'female';
module.exports = AnimalGender;