inheritance mixins & traits

Post on 07-Aug-2015

280 Views

Category:

Software

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

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

top related