Patrones de Diseño y JavaScript: Decorator

Dentro de este artículo se engloba el concepto definido como Decorator en el libro ‘Design Patterns’ del GoF, así también como los Function Decorators, que como su nombre lo indica, aplican únicamente a los objetos Function.

Este pattern pertenece al grupo de los patrones estructurales, y tiene como objetivo modificar el comportamiento de un objeto existente de manera dinámica. Obviamente, dada esta descripción podemos adivinar que no es un pattern con demasiada utilidad en Javascript, donde la modificación del comportamiento de los objetos durante el runtime es algo cotidiano.

Object Decorator

A diferencia de modificar directamente el objeto, este pattern lo envuelve con sucesivas capas que alteran su comportamiento de manera específica sin necesidad de codificar la clase o implementacion de cada combinación.

El clásico ejemplo en este caso es el del café, donde la implementación inicial puede decorarse con distintos aditivos, los cuales modifican el precio final del mismo. Como se menciono antes, el sujeto y sus decoradores presentan una misma interfaz, lo cual permite manipular los objetos resultantes de manera transparente (esto es, tratarlos de igual manera, estén decorados o no).

En este caso, tenemos el sujeto y sus posibles decoradores:

Sujeto y decoradores

Que luego podemos combinar para llegar a distintos resultados sin necesidad de implementar las distintas variantes.

Distintas combinaciones posibles de cafe

En código JavaScript podríamos representar el ejemplo de la siguiente manera:

// Subject
function Coffee() {
}

Coffee.prototype.cost = function() {
	return 5;
}

// Decorators

// Milk
function Milk(coffee) {
	this.coffee = coffee;
}

Milk.prototype.cost = function() {
	return this.coffee.cost() + 2;
}

// Whip
function Whip(coffee) {
	this.coffee = coffee;
}

Whip.prototype.cost = function() {
	return this.coffee.cost() + 2;
}

// Chocolate
function Chocolate(coffee) {
	this.coffee = coffee;
}

Chocolate.prototype.cost = function() {
	return this.coffee.cost() + 3;
}

// Sprinkles
function Sprinkles(coffee) {
	this.coffee = coffee;
}

Sprinkles.prototype.cost = function() {
	return this.coffee.cost() + 1;
}

// En accion

// Subject
var coffee = new Coffee();

coffee.cost(); // 5

// Y sus sucesivas decoraciones
coffee = new Milk(coffee);
coffee = new Chocolate(coffee);
coffee = new Whip(coffee);

coffee.cost(); // 12

Function decorator

La otra realidad de este pattern, es su aplicación en funciones, situación que se ve favorecida por las formas y sintaxis de JavaScript. En principio la idea es la misma que con los objetos, que consiste en envolver el subject con comportamiento agregado sin necesidad de tener que modificar o generar nuevas implementaciones.

Dentro de la documentación de Akshell (una implementación de SSJS) encontramos esta breve y concisa definición:

Decorator

A function accepting a function and returning another function, usually used as a function transformation.

Un caso de uso real puede ser el del scope binding en event handlers, en el cual se decora una función para asegurar su ejecución en el contexto deseado:

element.addEventListener(
	'click',
	// Devolvemos una funcion que se ejecutara
	// como metodo de myObject
	bindDecorator(myHandler, myObject)
);

De igual manera que con los objetos, podemos decorar con sucesivos comportamientos específicos sin necesidad de conocer la implementación real de la función original. Obviamente el resultado final se ve afectado por la naturaleza y orden de los decoradores aplicados.

// Decora la funcion para que reciba a Chuck
// como primer argumento
function chuckNorrisDecorator(fn) {
	return function() {
		var args = [].slice.call(arguments);

		args.unshift('Chuck Norris');

		return fn.apply(null, args);
	}
}

// Decora la funcion para que todos sus
// argumentos string sean uppercase
function upperCaseArgumentDecorator(fn) {
	return function() {
		for(var i = 0, l = arguments.length; i < l; i++) {
			if(typeof arguments[i] == 'string' ) {
				arguments[i] = arguments[i].toUpperCase();
			}
		}
		return fn.apply(null, arguments);
	}
}

// Saludador
function welcome(name) {
	console.log('Welcome ' + name);
}

welcome('Rod'); // "Welcome Rod"

// Un saludador exaltado
var upperCaseWelcome = upperCaseArgumentDecorator(welcome);

upperCaseWelcome('Rod'); // "Welcome ROD"

// Un saludador pre completado
var welcomeChuck = chuckNorrisDecorator(upperCaseWelcome);

welcomeChuck(); // "Welcome CHUCK NORRIS"

Casos más concretos pueden ser:

Por otro lado, se pueden lograr herramientas complejas por medio de la decoración de funciones. Sin duda alguna, el blog de Angus Croll tiene unos cuantos casos de uso. Imaginemos una herramienta que permita autogenerar tests de nuestros objetos de manera recursiva, o un tracer que se adapte a nuestra compleja jerarquía de objetos.

Estos dos ultimos casos basan su funcionalidad en recorrer recursivamente el objeto en cuestión reemplazando sus funciones por la decoración deseada, logrando mantener la funcionalidad original.

Trace en accion

Trace en accion

Leer más:


Shortlink: http://goo.gl/Ayadm




3 views shared on this article. Join in...

  1. John Acosta dice:

    Excelente artículo compañero :)



Pings to this post

  1. [...] implementar este tipo de funcionalidad, basta con echar mano a los function decorators, tema del cual se hablo con anterioridad en este blog. La idea del function decorator es crear una función que envuelva a otras, alterando o no el [...]


Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *

Comment

You may use these tags : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>