41
When Inheritance is not enough. Mixins and others ways to reuse code. Maximiliano Fierro @elmasse

Inheritance Mixins & Traits

Embed Size (px)

Citation preview

Page 1: Inheritance Mixins & Traits

When Inheritance is not enough. Mixins and others

ways to reuse code.Maximiliano Fierro

@elmasse

Page 2: Inheritance Mixins & Traits

Maximiliano Fierro

Sr. Solution Engineer

Co-Organizer

Page 3: Inheritance Mixins & Traits

Sharing Code in OOP

Page 4: Inheritance Mixins & Traits

ECMAScript is an Object Oriented Programing Languagesupporting delegating inheritance based on prototypes.

Page 5: Inheritance Mixins & Traits

What does it mean?

Page 6: Inheritance Mixins & Traits

Every Object references a Prototype

[[Prototype]]: Internal property pointing to the object prototype.

Some implementations provide the non-standard __proto__ as explicit reference.

Page 7: Inheritance Mixins & Traits

// 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

...

Page 8: Inheritance Mixins & Traits

// 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

Page 9: Inheritance Mixins & Traits

“Using inheritance as a vehicle to reuse code is a bit like ordering a

happy meal because you want the plastic toy”

Angus Croll.

Page 10: Inheritance Mixins & Traits

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.

Page 11: Inheritance Mixins & Traits

ProblemsAbstract/Base Classes Deep hierarchy.

The Base/Abstract Class becomes a “bag of common functions”.

Page 12: Inheritance Mixins & Traits

Reusing FunctionsEvery (public) function can be invoked with .call

or .apply

Page 13: Inheritance Mixins & Traits

It’s OK for simple functions...var max = Math.max.apply(null, array);

Page 14: Inheritance Mixins & Traits

… 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

Page 15: Inheritance Mixins & Traits

MixinsLet’s share some code!

Page 16: Inheritance Mixins & Traits

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.

Page 17: Inheritance Mixins & Traits

Mixins: BenefitsEncourage code reuse.Can be seen as a workaround for Multiple Inheritance.Avoid the inheritance ambiguity (The diamond problem) linearly.

Page 18: Inheritance Mixins & Traits

ImplementationUsing Object.assign (or polyfill)- The Mixin is merged into the Class

prototype.- Mixin can be Class or Object

Page 19: Inheritance Mixins & Traits

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()

Page 20: Inheritance Mixins & Traits

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’);

Page 21: Inheritance Mixins & Traits

Functional MixinsMixin is not a class but a function that decorates the

prototype thru “this” keyword.

Page 22: Inheritance Mixins & Traits

// 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

Page 23: Inheritance Mixins & Traits

The Diamond ProblemBaseClass

ClassA ClassB

DerivedClass

foo()

var d = new DerivedClass();

d.foo() // which foo ??

...

...

...foo()

Page 24: Inheritance Mixins & Traits

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

Page 25: Inheritance Mixins & Traits

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);}

Page 26: Inheritance Mixins & Traits

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.

Page 27: Inheritance Mixins & Traits

TraitsSort of “Smart Mixins”

Page 28: Inheritance Mixins & Traits

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.

Page 29: Inheritance Mixins & Traits

Class = Traits + Glue Code + State (+ SuperClass)

Page 30: Inheritance Mixins & Traits

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)

Page 31: Inheritance Mixins & Traits

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.

Page 32: Inheritance Mixins & Traits

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

Page 33: Inheritance Mixins & Traits

MyList.prototype

getCollection()

[[Prototype]]

first()

last()

iterator()

[[Prototype]]

collection: [1,2,3,4,5]

list

Page 34: Inheritance Mixins & Traits

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>

Page 35: Inheritance Mixins & Traits

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()

Page 36: Inheritance Mixins & Traits

Conflict Resolution:Alias or Exclude

we can exclude “first” from the iterable Trait or rename it in case we want to use it.

Page 37: Inheritance Mixins & Traits

ImplementationLibraries• Traits.js• CocktailJS• (many others in npm)

Page 38: Inheritance Mixins & Traits

CocktailJSnpm install -s cocktailcocktailjs.github.io

Page 39: Inheritance Mixins & Traits

Annotations. Traits & Talents

Page 40: Inheritance Mixins & Traits

Demo

Page 41: Inheritance Mixins & Traits

Thanks!@elmasse

github.com/elmasse