fundamentos poo en js. mixins y traits

59
Fundamentos de POO en JS; Mixins y otras técnicas de reutilización Maximiliano Fierro @elmasse

Upload: maximiliano-fierro

Post on 28-Jul-2015

118 views

Category:

Software


0 download

TRANSCRIPT

Fundamentos de POO en JS; Mixins y otras técnicas de

reutilizaciónMaximiliano Fierro

@elmasse

Maximiliano Fierro

Solutions Architect

ECMAScript es un lenguaje Orientado a Objetos

con herencia basada en delegación de prototipos

¿Qué significa?

Todo Objeto referencia a un prototipo

¿Qué es un Objeto?Un conjunto no ordenado de pares clave-valor.

Estos pares son llamados propiedades.Cuando el valor de una propiedad es una función decimos

que es un método.

var obj = { msg : “hola”};

obj

msg “hola”

[[Prototype]]

Object.prototype

[[Prototype]]

null

[[Prototype]]

Propiedad interna que apunta al prototipo del objeto. Algunas implementaciones proveen la propiedad __proto__ (no-standard) como referencia explícita.

obj.__proto__> Object.prototype

Esto nos da acceso a las propiedades y métodos definidos en Object.prototype:

obj.toString();obj.hasOwnProperty();

etc..

Prototype ChainEs el mecanismo de herencia en JavaScript.

Cuando le pedimos una propiedad a un objeto, se busca primero entre las propiedades del objeto y luego si no existe, se sigue la cadena de

[[Prototype]] hasta encontrarlo.

ClasesLos objetos se crean mediante constructores.

Un constructor es una función que va a ser llamada con new.El constructor tiene asociado un objeto llamado prototype.El prototype se utiliza para definir propiedades de instancia.

Por defecto, el prototype es una instancia de Object, por lo tanto, su [[Prototype]] apunta al Object.prototype.

constructorSe utiliza para crear e inicializar un objeto.

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

...

Herencia por PrototiposLa herencia se obtiene encadenando el prototype de

la clase con una instancia de la clase Padre.

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

Object.create

Object.create = function(Prototype) { return { __proto__: Prototype };};

Fácil, ¿no?

No tan rápido.Hay algunos pequeños detalles que debemos tener

en cuenta, en especial al definir “propiedades”

El prototype es un objeto.Accedemos al mismo prototype desde todas las

instancias de nuestra clase.

// Constructorfunction MyClass () {};

MyClass.prototype.array = [];

// Instance methodMyClass.prototype.foo = function () { console.log(this.array);};

var a = new MyClass();var b = new MyClass();

a.foo(); // []

b.array.push(1);b.array.push(2);a.array.push(3);

a.foo(); // [1,2,3] ???b.foo(); // [1,2,3] ???

a

[[Prototype]]

MyClass.prototype

foo fn()

array [1,2,3]

[[Prototype]]

> Object.protoype

b

[[Prototype]]

¿Entonces?No definan propiedades en el prototype.

Usen el constructor. Después de todo, es el encargado de inicializar el objeto.

// Constructorfunction MyClass () { this.array = [];};

// Instance methodMyClass.prototype.foo = function () { console.log(this.array);};

var a = new MyClass();var b = new MyClass();

a.foo(); // []

b.array.push(1);b.array.push(2);a.array.push(3);

a.foo(); // [3]b.foo(); // [1,2]

a

array [3]

[[Prototype]]

MyClass.prototype

foo fn()

[[Prototype]]

> Object.protoype

b

array [1,2]

[[Prototype]]

Reutilizando Código en Programación Orientada a

Objetos

“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*Class / *Base*ClassUsualmente cuando utilizamos algún framework que provee clases de las cuales tenemos que extender (por ej. Views, Components, Controllers, etc.) y nos encontramos código que se repite entre las subclases.

Problemas• Agregamos niveles de herencia.• La clase Base/Abstracta se vuelve una

bolsa de funciones comunes.• Las subclases acceden a mas funcionalidad

de la realmente requerida.• Refactorizar se vuelve mas complejo.

Reutilizando FuncionesToda función puede ser invocada con .call

o .apply (Borrowing)

“OK” en funciones simples...var max = Math.max.apply(null, array);

… pero el código se vuelve verborrágicovar 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

MixinsOtra formas de reutilizar código

¿Qué es un Mixin?Un Mixin es una clase que, por lo general, no está pensada para ser instanciada o extendida, sino combinada en otra clase.

El Mixin es combinado en la clase a través de un merge.

En JavaScript, podemos usar Objects como Mixins.

Mixins: BeneficiosIncentivan la reutilización de código.Pueden ser utilizados como una solución alternativa a la herencia múltiple.Evitan la ambigüedad de herencia (The diamond problem) linealmente.

ImplementaciónObject.assign (o polyfill)- El Mixin es combinado en el prototipo de

la Clase.- Un Mixin puede ser una Clase o un Objeto.

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 como 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 MixinsEl Mixin no es una clase sino una función que decora el

prototipo a través de “this”.

// 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() // ¿¿ cuál foo ??

...

...

...foo()

The Diamond ProblemObject.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

Llamar a un Método Sobre-escrito….Object.assign(DerivedClass.prototype, ClassA.prototype, ClassB.prototype);

DerivedClass.prototype.foo = function() { // call “overridden” foo from A ClassA.prototype.foo.apply(this, arguments);}

ProblemasMétodos y propiedades son sobre-escritos basados en la posición de la declaración.

Refactoring (agregar/renombrar métodos) puede causar “problemas silenciosos”.

Llamar a un metodo sobre-escrito es verborragico y propenso a errores.

TraitsUna especie de “Mixins Inteligentes”

¿Qué es un Trait?Unidades de comportamiento que se pueden componer.Un tipo especial de clases sin estado.Se los puede ver como clases incompletas.Definen comportamiento y acceden al estado a través de métodos requeridos.

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

Componiendo ComportamientoPuede requerir un conjunto de métodos que servirán como parámetros del comportamiento ofrecido. (Clase incompleta)

Pueden ser compuestos por otros traits.

La colisión de nombres debe ser resuelta por el desarrollador (usando alias y exclusiones)

Resolución de ConflictosUn Conflicto aparece cuando combinamos dos o mas traits que provean métodos llamados idénticamente que no se originen en el mismo trait.Alias:Un método conflictivo se puede “renombrar".Exclusión: Podemos resolver el conflicto excluyendo el método en cuestión.

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

Resolución de Conflictos:Alias o Exclude

Podemos usar exclude en “first” del Trait iterable o alias para renombrarlo en caso que queramos

usarlo (Una alternativa de llamar al metodo sobre-escrito)

ImplementacionesLibrerías:• Traits.js• CocktailJS• (varias más en npm)

CocktailJSnpm install -s cocktail

cocktailjs.github.io

Annotations. Traits & Talents

Demo

Gracias!@elmasse

github.com/elmasse