Inheritance Mixins & Traits
-
Upload
maximiliano-fierro -
Category
Software
-
view
280 -
download
0
Transcript of Inheritance Mixins & Traits
When Inheritance is not enough. Mixins and others
ways to reuse code.Maximiliano Fierro
@elmasse
Maximiliano Fierro
Sr. Solution Engineer
Co-Organizer
Sharing Code in OOP
ECMAScript is an Object Oriented Programing Languagesupporting delegating inheritance based on prototypes.
What does it mean?
Every Object references a Prototype
[[Prototype]]: Internal property pointing to the object prototype.
Some implementations provide the non-standard __proto__ as explicit reference.
// Constructorfunction MyClass (msg) { this.msg = msg;};
// Instance methodMyClass.prototype.foo = function () { console.log(this.msg);};
var a = new MyClass(‘Hello!’);
a.foo(); // Hello!
MyClass.prototype
foo()
[[Prototype]]
msg
a
[[Prototype]]
[[Prototype]]
Object.prototype
null
...
// Constructorfunction ClassA () { // call parent constructor MyClass.apply(this, arguments);};
// inherit from MyClassClassA.prototype = Object.create(MyClass.prototype);
// Instance methodClassA.prototype.bar = function () { console.log(this.msg + ‘ from bar’);};
var obj = new ClassA(‘Hello!’);obj.foo(); // Hello!obj.bar(); // Hello! from bar
MyClass.prototype
foo()
ClassA.prototype
bar()
[[Prototype]]
[[Prototype]]
msg
obj
“Using inheritance as a vehicle to reuse code is a bit like ordering a
happy meal because you want the plastic toy”
Angus Croll.
Abstract* / Base* ClassesWhen inheriting from a Framework / Library Class (e.g. Views, Components, Controllers) and you find common methods or repeated code in several subclasses, often a Base Class is created to “share” that common code.
ProblemsAbstract/Base Classes Deep hierarchy.
The Base/Abstract Class becomes a “bag of common functions”.
Reusing FunctionsEvery (public) function can be invoked with .call
or .apply
It’s OK for simple functions...var max = Math.max.apply(null, array);
… but code becomes wordyvar instance = { msg: ‘Hello from instance’};
//borrowing foo method from MyClassMyClass.prototype.foo.apply(instance); // Hello from instance
//borrowing bar method from ClassAClassA.prototype.bar.apply(instance); // Hello from instance from bar
//borrowing foo method from ClassA inherited from MyClassClassA.prototype.foo.apply(instance); // Hello from instance
MixinsLet’s share some code!
What is a Mixin?A Mixin is a class that is, often, not intended to be instantiated, but is combined into another class.
Usually it is merged into a Class.
In JavaScript, we can use Objects as Mixins.
Mixins: BenefitsEncourage code reuse.Can be seen as a workaround for Multiple Inheritance.Avoid the inheritance ambiguity (The diamond problem) linearly.
ImplementationUsing Object.assign (or polyfill)- The Mixin is merged into the Class
prototype.- Mixin can be Class or Object
function MixedClass () { // call mixin constructor MyClass.apply(this, arguments);};
// apply MixinObject.assign(MixedClass.prototype, MyClass.prototype);
// Instance methodMixedClass.prototype.bar = function () { console.log(this.msg + ‘ from bar’);};
var obj = new MixedClass(‘Hello!’);
obj.foo(); // Hello!obj.bar(); // Hello! from bar
MixedClass.prototype
foo()
[[Prototype]]
msg
obj
[[Prototype]]
[[Prototype]]
Object.prototype
null
...
bar()
EventEmitter as a Mixinfunction MyClass() { // call Mixin constructor EventEmitter.call(this, arguments);}Object.assign(MyClass.prototype, EventEmitter.prototype);
var obj = new MyClass();
obj.on(‘event’, function ( ) { console.log(‘event!’); });obj.emit(‘event’);
Functional MixinsMixin is not a class but a function that decorates the
prototype thru “this” keyword.
// Functional Mixinfunction WithFoo() {
this.foo = function () { console.log(this.msg); } return this;}
// MixedClass definitionfunction MixedClass(msg) { this.msg = msg;}
// Apply MixinWithFoo.call(MixedClass.prototype);
MixedClass.prototype.bar = function () { console.log(this.msg + ‘ from bar’);};
var obj = new MixedClass(‘Hello!’);
obj.foo(); // Hello!obj.bar(); // Hello! from bar
The Diamond ProblemBaseClass
ClassA ClassB
DerivedClass
foo()
var d = new DerivedClass();
d.foo() // which foo ??
...
...
...foo()
The Diamond Problem cont’dObject.assign(DerivedClass.prototype, ClassA.prototype, ClassB.prototype);
var d = new DerivedClass();
d.foo() // from ClassB
Object.assign(DerivedClass.prototype, ClassB.prototype, ClassA.prototype);var d = new DerivedClass();
d.foo() // from ClassA
Calling an Overridden Method….Object.assign(DerivedClass.prototype, ClassA.prototype, ClassB.prototype);
DerivedClass.prototype.foo = function() { // call “overridden” foo from A ClassA.prototype.foo.apply(this, arguments);}
ProblemsMethods and properties are overridden based on declaration position / implementation.
Refactoring (adding/renaming methods) can cause “silent issues”.
Calling an overridden method is wordy and error prone.
TraitsSort of “Smart Mixins”
What is a Trait?Composable Units of Behavior.A special type of class with no state.Can be seen as Incomplete classes.Defines behavior and access state thru required accessors.
Class = Traits + Glue Code + State (+ SuperClass)
Composing BehaviorMay require a set of methods to serve as parameters for the provided behavior. (Incomplete Classes)
Can be composed from other traits.
Name collision is solved by the Developer (aliases or exclusions)
Conflict ResolutionConflict arises when we combine two or more traits providing identically named methods that do not originate from the same trait.
Alias: A conflicting method can be “renamed”.
Exclusion: We can solve the conflict by just excluding the method explicitly.
MyList.prototype
+getCollection()
[[Prototype]]
withFirst <Trait>
first()
*getCollection()
withLast <Trait>
last()
*getCollection()
withIterator <Trait>
iterator()
*getCollection()
@traits
[[Prototype]]
collection: [1,2,3,4,5]
list
var list = new MyList([1,2,3,4,5]);
console.log('collection: ', list.getCollection()); // [1,2,3,4,5]
console.log('first: ', list.first()); // 1console.log('last: ', list.last()); // 5
console.log('iterate:');
var iterator = list.iterator();var value;
while(value = iterator.next()){ console.log(value);}
(*) Required Method(+) Glue Code
MyList.prototype
getCollection()
[[Prototype]]
first()
last()
iterator()
[[Prototype]]
collection: [1,2,3,4,5]
list
MyList.prototype
getCollection(): {Array}
[[Prototype]]
withFirst <Trait>
first()
*getCollection()
withLast <Trait>
last()
*getCollection()
withIterator <Trait>
iterator()
*getCollection()
@traits
[[Prototype]]
collection: [1,2,3,4,5]
list
var list = new MyList([1,2,3,4,5]);
console.log('collection: ', list.getCollection()); // [1,2,3,4,5]
console.log('first: ', list.first()); // 1console.log('last: ', list.last()); // 5
console.log('iterate:');
var iterator = list.iterator();var value;
while(value = iterator.next()){ console.log(value);}
iterable <Trait>
MyList.prototype
getCollection(): {Array}
[[Prototype]]
withFirst <Trait>
first()
*getCollection()
withLast <Trait>
last()
*getCollection()
withIterator <Trait>
iterator()
*getCollection()
@traits
[[Prototype]]
collection: [1,2,3,4,5]
list
ERROR! method “first” is defined twice!
iterable <Trait>
first()
Conflict Resolution:Alias or Exclude
we can exclude “first” from the iterable Trait or rename it in case we want to use it.
ImplementationLibraries• Traits.js• CocktailJS• (many others in npm)
CocktailJSnpm install -s cocktailcocktailjs.github.io
Annotations. Traits & Talents
Demo
Thanks!@elmasse
github.com/elmasse