While it's not the type of inheritance JS users are accustom to when using some form of extend(), I never really thought that version of inheritance was of much use. There isn't any real way to ensure that mixing 2 or more objects together won't cause errors in other code that isn't expecting the mix. What's more is that this form of mixing fails if the destination object being mixed in contains properties using calculations or is sealed or frozen.
I won't go into the reasons why multiple inheritance has been frowned on by so many leaders in the industry. It's not that I agree with that line of thinking either. However, prototype inheritance doesn't really lend itself to multiple inheritance. Where prototypes are concerned, there can be only 1. Any attempt to implement multiple inheritance would have to be in the form of a "has-a" relationship vs the "is-a" relationship that's normally expected with inheritance. While there is a way around this, I'm still working out the details. For now, let's talk about how the existing inheritance policy works.
Inheritance
Here's an example to get us started. This example shows an example of basic inheritance in Java and its equivalent in JS using Class.js.Java
public class SampleBase {
...
}
public class Sample extends SampleBase {
...
}
Class.js
var SampleBase = new Class("SampleBase", {
...
});
var Sample = new Class("Sample", {
Extends: SampleBase,
...
});
Much like Java, Class.js uses the word Extends to introduce a base class. This should not in any way be confused with the extend() method commonly employed by most other class libraries. This Extends element allows the object to properly inherit the prototype of the specified Class definition. That is to say, the object assigned as the Extends element in the class definition object must itself be a constructor as returned by new Class(...).
As yet, Class.js definition objects cannot inherit from objects defined by other class libraries or other constructor functions. This is primarily because no other library as yet possesses the facilities to provide private and protected scopes that are compatible with Class.js. In fact, no other class libraries currently possess the facilities to create protected scopes at all! This feature is currently unique to Class.js. Not even the current ES6 recommendations support this capability!
Suppose SampleBase had a public or protected memeber "foo". As expected, all public and protected members of SampleBase would be available to Sample instances through the "this" keyword. Likewise, as expected, protected members of Sample and SampleBase will not appear to exist on the variable returned by calling new Sample().
Since neither Sample nor SampleBase declare Constructor functions, this code will run just fine, but what happens if SampleBase declares a constructor?
this.Super
If a constructor is declared by the base class, it is not called automatically. Why? There's no way to know what parameters to pass it! Class.js leaves such issues to the developer and provides "this.Super" to facilitate the capability.- this.Super(<args>) - Calls the base class Constructor function. This should be the first function called by any derived class. It's optional if the base class doesn't declare Constructor, but as a matter of good form and practice, call it anyway.
- this.Super.<member> - accesses any Protected or Public member of the base class, unmodified by the current derived class.
Interfaces
As I previously stated, prototype inheritance doesn't do multiple inheritance all that well. So, like with Java, the easiest thing to implement is Interface support. Here's the same example as before with interfaces added in.
Java
public interface foo {
...
}
public interface bar {
...
}
public interface fubar {
...
}
public class SampleBase {
...
}
public class Sample extends SampleBase implements foo, bar, fubar {
...
}
Class.js
var foo = { ... };
var bar = function() { ... };
bar.prototype = { ... };
var fubar = new Class("fubar", { ... });
var SampleBase = new Class("SampleBase", {
...
});
var Sample = new Class("Sample", {
Extends: SampleBase,
Implements: [ foo, bar, fubar ],
...
});
Once again, I borrowed from Java and used "Implements" as the key to list the interfaces implemented by the class. Behind the scenes, Class.js verifies that the public, enumerable interface exposed by each object in the Implements array has also been implemented in the class definition object. However, we run into that old multiple inheritance issue again. Given the code above, while in Java a Sample instance can be assigned to a variable of types foo, bar, and fubar, JS will not return true if you check (new Sample() instanceof foo). For Class.js, there is no way to do true multiple inheritance that can be recognized by the built-in JS operators.
<obj>.Implements(<arg>)
Since the built-in operators are insufficient and cannot be overridden without hacking the JS engine in use, the only thing left to do is to create new functions to do the same work. Implements() is such a function. This function performs the exact same check as was done for each element in the Implements array. The only argument is an object or function with a public interface. Implements() returns true if <obj>'s definition contains matching interfaces for all public parts of the argument.
As it stands, I'm still considering implementing full multiple inheritance using the same method. This means that instanceof() will no longer be useful, but that can be replaced by a factory method in Class.js. I wish there was some way of overloading the instanceof() function to support multiple inheritance. With that, you can look forward to part 4 describing the tools available to you when implementing member functions in the class definition object. If I decide to implement multiple inheritance, I'll mention it then too.
No comments:
Post a Comment