dcl()
Version 1.x
dcl()
is the “class” composition helper, which simplifies creating constructors for new classes of objects:
- It reduces a boilerplate to set up a single inheritance.
- In case of mixin-based multiple inheritance it correctly linearizes all dependencies.
- While composing constructors, it can process advanced features:
- Super calls, when you need to call a method of a super class (a base).
- AOP advices.
- Automatic chaining of arbitrary methods.
Of course, an experienced programmer can do all this stuff manually, but dcl()
offers a less error-prone and more
compact way to achieve what you need. But, just in case, it will play nice with hand-made constructors, so your
constructors can inherit from them, or mix them in as mixins.
dcl()
is a main function of the whole package. It is defined in mini.js and can be extended by
dcl.js. Both these modules return it as their value.
Description
1 2 3 |
|
Arguments and a return value:
base
is a base “class”. It can be one of three:null
- no base “class” ⇒ base it directly onObject
.- constructor object - a function created with
dcl
or a generic JavaScript constructor function. New “class” will be based on it using a single inheritance. - array of constructors - a C3 superclass linearization will be performed on this array, and the result will be used to create a new constructor using a single inheritance. This array should be non-empty.
props
is an object, whose properties are used to specify unique features of the “class” — methods, and class-level variables. Following properties have a special meaning fordcl()
:constructor
- an optional function, which will be called when an object is created. A base constructor (if any) is always called before a derived constructor. There is no restrictions on what arguments can be used for a constructor. A return value of constructor (if any) is ignored.declaredClass
- an optional human-readable string, which is used to identify the created class in error messages or logs. See debug.js for more details.
dcl()
returns a constructor created according to user’ specifications.
dcl()
is returned by mini.js and dcl.js and used to host additional properties. Look at
specific modules to learn what public properties are available.
Examples
base
is null
In this case produced “classes” are derived directly from Object
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Single inheritance
This is a familiar scenario for most programmers: we build on an existing “class” producing a new “class”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
|
We can base our classes on normal JavaScript constructors as well:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
|
Multiple inheritance with mixins
The dreaded diamond — the bane of multiple inheritance.
dcl()
deals with it with ease:
1 2 3 4 5 6 7 8 9 |
|
A triangle will be handled too:
1 2 3 4 5 6 7 |
|
Generally a “class”, which inherits from an array, tries to use the first item as its base. In some cases it is not possible due constraints on relative position of all bases. If that is the case, the innermost base class of the first item is used.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
If F
can be based on D
, our right-most list of dependencies should look like this: D, B, A
, which is not the case.
dcl()
cannot preserve this list because C
should go before B
as defined in E
. It forces dcl()
to base F
directly on A
.
For available debugging facilities take a look at debug.js and its log() method.
If you want to test if an object is inherited directly (in JavaScript sense) or indirectly (e.g., as a mixin), consider using isInstanceOf(), which is defined in dcl.js.
Notes
- Constructors are chained using “after” chaining, meaning that a derived constructor will be called only after its base constructor.
- Missing constructors are treated as empty constructors logically.
- All constructors are called with the same set of arguments.
- If a constructor returns a value, it will be ignored.
- All unchained methods (the default), and other properties, override properties with the same name in base classes.
- Always use
new
keyword when creating objects with a constructor produced bydcl()
. - Methods in
props
will be decorated with a meta information, and possibly copied. Because of that it is not recommended to reuse them for different classes. In generalprops
should be an object literal.dcl()
will assume full control over it.
FAQ
What is the difference between mini.js
and dcl.js
modules and when should I use mini.js
?
Both mini.js and dcl.js return the same object. mini.js
provides the core functionality
(mixin support, and super calls), while dcl.js
adds chaining, and class-level AOP advices. By default, when you
request dcl
, it loads dcl.js
:
1 2 |
|
If you application has very tight bandwidth constraints, and doesn’t use advanced features (e.g., small application
targeting mobile browsers), you may want to request mini.js
explicitly (assuming
AMD):
1 2 3 4 |
|
How can I detect, if my class inherits directly or indirectly from A
?
You can always use isInstanceOf(), which is defined in dcl.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
How does dcl()
linearize superclasses?
dcl()
uses the C3 superclass linearization algorithm. This is the
same proven algorithm that powers Python (since 2.3), Perl 6, Parrot, and Dylan. The best discussion of this algorithm
I know of is in this paper: The Python 2.3 Method Resolution Order.
Effectively this algorithm is a topological sorting of an inheritance graph with complexity O(n).
Why do I need the linearization? I don’t do no “diamonds” in my code.
Unfortunately dependency loops, and reversed dependencies can happen accidentally, especially in big projects, which are
targeted by this OOP package. It is very difficult to keep track of all mixins, and their dependencies, especially when
they are written by other programmers, or even 3rd-party shops. dcl()
keeps track of all these matters for you
linearizing bases, eliminating duplicates, and checking the overall consistency of your “classes”.
Many JS OOP packages skip the correct linearization step hoping that everything will be right as is. Unfortunately it can be a source of extremely hard-to-find bugs. Besides mixins use inheritance routinely to indicate their relative dependencies so duplicates will occure naturally.
I write only small programs. I don’t need no linearization, right?
If you write only small programs, chances are you don’t need OOP. See discussion of the OOP applicability area in OOP and JS, specifically “fail #2”.
Can I use hand-made constructors as bases or mixins with dcl()
?
Yes, you can. It was demonstrated in the code example above. Obviously your hand-made classes cannot use dcl()
-based
facilities like super calls, or class-level advices, but other than that they can be used without restrictions.
Is it possible to chain methods other than constructor?
Yes. See chainBefore() and chainAfter() directives provided by dcl.js.
Is it possible to use advices with constructors?
Yes. The full set of advices can be used with constructors.
Is it possible to “break” a chain of constructors or other chained methods?
While it is not advised due to possible violation of object invariants, and potential maintenance problems, you can do it with super calls — just define a super call and doesn’t call a super.
See superCall() for more details.
See discussion of object invariants in OOP and JS and in OOP in JS revisited.
Can I use []
instead of null
as my base?
No, it wouldn’t work.
In any case null
is a constant, which is shared, while []
is a newly-created object. The latter comes with
a penalty (it has to be created, and it will add a load to the garbage collector afterwards. null
is cheaper, and
clearly demonstrates programmer’s intent.
Can I use [base]
instead of base
as my base?
Yes, but why? The former will create an additional array object, which will be discarded right after the dcl()
call
increasing the load on the garbage collector. The latter is clearly cheaper, and more intentional.