js dp: 1. factory pattern

Una fábrica escalable de objetos

Jacob Lawrence

archivado en: JavaScript / 14 Septiembre, 2016 / taller:

Hace dos o tres años comencé un taller sobre javaScript orientado a objetos, cuya segunda parte, que no me dio tiempo a comenzar, debía de tratar sobre patrones de diseño. Retomo el tema, pero voy a tratarlo como un taller aparte, pues espero que los artículos correspondientes tengan otro aire. Y sin más, al lío ^^.

Introducción

En programación se conocen como «patrones de diseño» (design patterns) a fórmulas de desarrollo reutilizables que solucionan de forma efectiva y demostrada problemas comunes. Por ejemplo, todos los coches comparten una misma problemática y es que deben ser conducidos por personas, al menos de momento, las cuales solo disponen de dos herramientas ágiles para desempeñar tareas, que son las manos. Para resolverlo podemos usar un patrón de diseño denominado volante, que será similar al patrón timón que se usaba en los barcos. Ese patrón se podrá adaptar y habrá volantes circulares, semicirculares, forrados de cuero, de plástico y cuanta variante se nos ocurra, pero la estructura esencial será la misma. En palabras de  Christopher Alexander: «Each pattern describes a problem which occurs over and over again in our environment, and then describes the core of the solution to that problem, in such a way that you can use this solution a million times over, without ever doing it the same way twice».

En 1994  Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides -cuatro autores conocidos como el grupo Gang of Four (Gof)- publicaron un libro titulado Design Patterns: Elements of Reusable Object-Oriented Software, en el que sentaron las bases de los patrones de diseño, al menos, en la programación orientada a objetos. Según estos autores, un patrón de diseño debe constar de cuatro elementos principales:

  1. «The pattern name is a handle we can use to describe a design problem, its solutions, and consequences in a word or two. Naming a pattern immediately increases our design vocabulary. It lets us design at a higher level of abstraction. Having a vocabulary for patterns lets us talk about them with our colleagues, in our documentation, and even to ourselves. It makes it easier to think about designs and to communicate them and their trade-offs to others. [...].
  2. »The problem describes when to apply the pattern. It explains the problem and its context. It might describe specific design problems such as how to represent algorithms as objects. It might describe class or object structures that are symptomatic of an inflexible design. Sometimes the problem will include a list of conditions that must be met before it makes sense to apply the pattern.
  3. »The solution describes the elements that make up the design, their relationships, responsibilities, and collaborations. The solution doesn't describe a particular concrete design or implementation, because a pattern is like a template that can be applied in many different situations. Instead, the pattern provides an abstract description of a design problem and how a general arrangement of elements (classes and objects in our case) solves it.
  4. »The consequences are the results and trade-offs of applying the pattern. Though consequences are often unvoiced when we describe design decisions, they are critical for evaluating design alternatives and for understanding the costs and benefits of applying the pattern. The consequences for software often concern space and time trade-offs. They may address language and implementation issues as well. Since reuse is often a factor in object-oriented design, the consequences of a pattern include its impact on a system's flexibility, extensibility, or portability. Listing these consequences explicitly helps you understand and evaluate them».

A partir de estas premisas elaboraron un catálogo que todavía sigue siendo una referencia hoy en día, el cual consta de 24 patrones de diseño clasificados según el elemento que protagoniza el algoritmo (una clase o un objeto) y su propósito, que puede ser:

  • Generador (creational).
  • Estructural (structural).
  • Comportamiento (behavioral).
propósito
generador estructural comportamiento
elemento clase
  • Factory Method
  • Adapter
  • Interpreter Template Method
objeto
  • Abstract Factory
  • Builder
  • Prototype
  • Singleton
  • Adapter
  • Bridge
  • Composite
  • Decorator
  • Facade
  • Flyweight
  • Proxy
  • Chain of Responsibility
  • Command
  • Iterator
  • Mediator
  • Memento
  • Observer
  • State
  • Strategy
  • Visitor

Muchos patrones propuestos por el Gang of Four se han adaptado a javaScript, aunque hasta hace nada, claro está, según las posibilidades de ecmaScript 5, una versión que carecía de clases y otras características muy potentes que vienen de fábrica en la 6, aka harmony. En cualquier caso, creo que es interesante conocer tanto la versión clásica, como su versión harmónica y, en este sentido, termino esta introducción recomendando un libro que sigue siendo lectura indispensable como punto de partida sobre el tema: Learning JavaScript Design Patterns, de Addy Osmany, que se puede leer on-line en la web del autor.

Y dicho esto, vamos ya con nuestro primer patrón de diseño.

Factory pattern

Este patrón de diseño sirve para crear objetos y es muy útil cuando no estamos seguros al cien por cien sobre cómo deberán ser esos objetos al final o cuando su construcción es muy compleja. Además, permite encapsular métodos y propiedades, de tal manera que solo lo que se expone en el return de la función es accesible desde fuera. Se puede preparar de varias maneras y esta sería una de las más sencillas:

function Criaturas(tipoCriatura) {

function dormir() {

console.log('zzzzz');

}

return {

comer: function() {

console.log('soy un ' + tipoAnimal + ' que está comiendo');

}

}

}

var unicornio = Criaturas('unicornio');

/* Método accesible */

unicornio.comer();

/* Casca: dormir no es accesible */

unicornio.dormir();

Como vemos, en cierta manera equivaldría a crear un objeto con función constructora, pero sin usar el new. Bueno, en realidad, hay varias diferencias, pero profundizar ahora en ellas nos alejaría de la cuestión. Quedémonos por lo tanto solo con dos ideas claves:

  • una factoría sirve para crear objetos.
  • solo es accesible lo que se devuelve en el return.

Ahora bien, si este patrón solo sirviera para esto, no habría razón para usarlo en lugar de un objeto con función constructora, pero cuando realmente se atisba su potencial es cuando retomamos su funcionalidad clásica, esto es, como un interfaz para crear objetos mediante subclases auxiliares.

fabrica

 

Este diseño puede ser muy útil, como decía, cuando la construcción del objeto vaya a ser muy compleja o cuando no tenemos muy claro todas las variantes que puede tener. Retomando el ejemplo anterior, si dejamos el código como está, solo podemos crear un tipo de criaturas, los unicornios, pero, si necesitáramos también otro tipo de bichos y aún no tuviéramos claro cuáles serán, podríamos refactorizar el código y definir una factoría escalable que delegase en distintas subclases la creación del objeto según se pida un tipo u otro. Sería lo que en inglés se conoce como dispatcher, un término que no sé cómo diantres traducir al castellano. Algo así:

function Criaturas(tipoCriatura) {

let tipo = tipoCriatura.toLowerCase();

switch(tipo) {

case 'unicornio':

return Unicornios();

case 'gnomo':

return Gnomos();

default:

return null;

}

}

function Unicornios() {

return {

trotar: function() {}

}

}

function Gnomos() {

return {

husmear: function() {}

}

}

var unicornio = Criaturas('unicornio');

var gnomo = Criaturas('gnomo');

OJO, en este caso he usado funciones porque me ha parecido lo más didáctico, pero tanto la fábrica, como las subfábricas, pueden ser cualquier cosa que termine por generar un objeto. Por ejemplo, a mí, que me pone de los nervios ver algo «flotando», es decir, que no forme parte de un objeto o un módulo me gusta más algo parecido a esto:

const Criaturas = {

getCriatura: function (tipoCriatura) {

let nuevoBicho;

let tipo = tipoCriatura.toLowerCase();

switch(tipo) {

case 'unicornio':

nuevoBicho = new Unicornios();

break;

case 'gnomo':

nuevoBicho = new Gnomos();

break;

default:

return null;

}

nuevoBicho.metodoComun = function() {

console.log('zasprist');

}

return nuevoBicho;

}

};

const Unicornios = function() {

this.name = 'Bucéfalo';

}

Unicornios.prototype.trotar = function() {

console.log('trocotró');

}

let unicornio = Criaturas.getCriatura('unicornio');

unicornio.trotar();

unicornio.metodoComun();

Pero también se puede articular a partir de las nuevas clases de harmony, que queda de lo más elegante. Por ejemplo, una fórmula entre varias podría ser esta:

class Criaturas = {

satic getCriatura(tipoCriatura, options) {

let tipo = tipoCriatura.toLowerCase();

switch(tipo) {

case 'unicornio':

return new Unicornios();

case 'gnomo':

return new Gnomos();

break;

default:

return null;

}

}

};

class Unicornios = function() {

// lo que sea que tengan los unicornios

}

let unicornio = Criaturas.getCriatura('unicornio');

 

|| Tags: , ,

valoración de los lectores sobre js dp: 1. factory pattern

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (1 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

4 respuestas a “js dp: 1. factory pattern

Aportar un comentario

*