Cuando programamos en un lenguaje orientado a objetos siempre tenemos que crear objetos de clases que creamos e incluso de clases ya existentes.
El problema viene cuando tenemos objetos que tienen información similar, pero son divididos en clases distintas, es decir, que cada objeto se identifique por sus diferencias. Muchas confusiones se logran cuando se quiere instanciar de una cierta clase, pero eso se puede evitar utilizando un patrón de diseño.
¿Qué es un patrón de diseño?
Básicamente los patrones de diseño son soluciones a problemas sencillos del desarrollo de software. Hay patrones de diseño que se enfocan en distintas tareas, pero el que veremos aquí es un patrón creacional.
Los patrones creacionales ayudan a generar una forma flexible de crear objetos sin tener que recurrir a usar el operador new y el uso del constructor original de la clase.
Patrón de diseño “Factory”
El patrón de diseño “Factory” se enfoca en la creación de una interface que facilite la creación de objetos que se organizan por diferentes subclases. Esto ocurre con frecuencia cuando se hace uso de la herencia. Una clase abstracta se genera conteniendo los atributos generales, y después se crean clases para objetos específicos. Para evitar llamar constructores específicos se deben crear interfaces que nos ayuden en estas tareas.
En este patrón de diseño hay dos formas de generar las interfaces para crear objetos: El método Factory (Factory method) y el “Abstract Factory”.
Método Factory (Factory Method)
El método Factory es bastante sencillo de explicar e implementar. Se trata de un simple método agregado dentro de una clase que, por medio de un parámetro o varios parámetros, se decide qué tipo de objeto crear para entonces retornarlo y poder utilizarlo. Esto hace que no tengamos que importar numerosas clases y mucho menos instanciar muchos objetos de esas mismas clases.
Aquí se muestra un ejemplo elaborado en Javascript de cómo se emplea el método Factory. Hay que tomar en cuenta que en Javascript no existen las clases ni la herencia, por lo que todo se hace en base a funciones.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
//Nuestro "Factory Method" // Se enfoca en la elaboración de objetos que comparten la misma descripción // pero que se dividen en subclases para distinguirlos // Todos usan masa original y el mismo tipo de salsa, pero los ingredientes // son distintos. function Fabrica(){ this.crearPizza = function (tipo){ //Nuestro objeto genérico var pizza; //Aquí se decide el objeto de la subclase que se va a crear usando este método switch(tipo){ case 'original': pizza = new Original(); break; case 'especial': pizza = new Especial(); break; case 'vegetariano': pizza = new Vegetariano(); break; } //Atributos generales de todas las pizzas // Todas las pizzas comparten esta misma información // La única diferencia son los ingredientes pizza.tipo = tipo; pizza.masa = 'original'; pizza.salsa = 'tomate'; pizza.queso = 'mozzarela'; return pizza; } } var Original = function(){ this.ingredientes = 'peperoni'; }; var Especial = function(){ this.ingredientes = 'peperoni, tocino, salchicha italiana'; }; var Vegetariano = function(){ this.ingredientes = 'cebolla, pimiento'; }; //Iniciamos un array para almacenar varias pizzas var pizzas = []; //Creamos un objeto de la fábrica para generar las pizzas var fabriquita = new Fabrica(); //Una variable temporal para guardar las cadenas temporales con la información del objeto var temp = ''; //CREACIÓN DE LAS PIZZAS pizzas.push(fabriquita.crearPizza('especial')); pizzas.push(fabriquita.crearPizza('vegetariano')); pizzas.push(fabriquita.crearPizza('original')); pizzas.push(fabriquita.crearPizza('especial')); pizzas.push(fabriquita.crearPizza('original')); //Un bucle FOR para imprimir en alerts la información de cada objeto for (var i = 0; i < pizzas.length; i++) { temp = (i + 1) + '.-\nTipo: ' + pizzas[i].tipo + '\nIngredientes: ' + pizzas[i].ingredientes + '\nMasa: ' + pizzas[i].masa + '\nSalsa: ' + pizzas[i].salsa + '\nQueso: ' + pizzas[i].queso; alert(temp); temp = ''; } |
Aquí puedes probar el código en ejecución: LINK DE PRUEBA FACTORY METHOD
Aquí puedes descargar el código: LINK DE DESCARGA FACTORY METHOD
En este ejemplo se puede apreciar que tenemos la función llamada Fábrica, la cual se enfoca en crear y retornarnos un objeto pizza. El parámetro que recibe, llamado tipo, ayuda a decidir qué tipo de pizza necesitamos. Aquí se utiliza un switch para decidir de cuál clase (o en este caso, de cuál función) debemos instanciar nuestro objeto. Una vez decidido, se agregan los atributos que todas las pizzas tienen en común, que son la masa, la salsa y el queso que utilizan.
Cada función específica para los tipos de pizza simplemente agregan el atributo ingrediente y ese valor cambia dependiendo del tipo instanciado.
A la hora de crear las pizzas, podemos notar que no tuvimos que mandar a llamar ningún operador new ni nombrar ningún constructor que definimos para las pizzas. Ahora solamente creamos un objeto llamado fabriquita y éste se le manda a llamar a su método llamado crear pizza con un parámetro para ver qué objeto se debe crear y almacenar en el arreglo pizzas.
Abstract Factory
Al contrario del método Factory, El Abstract Factory se enfoca a tener una fábrica única para esos objetos. Se crean fábricas en clases separadas que se enfocan en crear un cierto tipo de objeto y entonces evitar llamar los constructores originales.
En el siguiente ejemplo se crean fábricas de objetos de tipo libro y revista. Ambos contienen el mismo tipo de campo, pero se tratan como objetos distintos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
//Creamos nuestra función que representa el "constructor" del objeto libro //Recibe de parámetro un valor que será su nombre function Libro(nombre){ this.nombre = nombre; } //Función que se dedica a crear el objeto libro por nosotros //Nos evita escribir el operador "new" para crear libros function fabricaLibro(){ this.crear = function(nombre){ return new Libro(nombre); }; } //Creamos nuestra función que representa el "constructor" del objeto revista //Recibe de parámetro un valor que será su nombre function Revista(nombre){ this.nombre = nombre; } //Función que se dedica a crear el objeto revista por nosotros //Nos evita escribir el operador "new" para crear revistas function fabricaRevista(){ this.crear = function(nombre){ return new Revista(nombre); }; } //Creamos un array que nos guarda los libros y revistas var biblioteca = []; //Tenemos un objeto de la fábrica de libros var fabriquitaLibro = new fabricaLibro(); //Tenemos un objeto de la fábrica de revistas var fabriquitaRevista = new fabricaRevista(); //Tenemos una variable temporal que nos sirve para guardar la información de los objetos para imprimirlo var temp = ''; //CREACIÓN DE LAS PUBLICACIONES biblioteca.push(fabriquitaLibro.crear('Harry Potter')); biblioteca.push(fabriquitaLibro.crear('Luna de plutón')); biblioteca.push(fabriquitaRevista.crear('MAD')); biblioteca.push(fabriquitaRevista.crear('Muy interesante')); //Búcle FOR que nos sirve para guardar los nombres de los artículos dentro de una variable for (var i = 0; i < biblioteca.length; i++) { temp += i + '.-' + biblioteca[i].nombre + '\n'; } //Imprimimos toda la información en un alert alert(temp); |
Aquí puedes probar el código en ejecución: LINK DE PRUEBA ABSTRACT FACTORY
Aquí puedes descargar el código: LINK DE DESCARGA ABSTRACT FACTORY
El código muestra funciones enfocadas a la creación individual de libros y revistas, al igual que fábricas para crear dichos objetos. Este patrón de diseño ayuda a dejar más claro el qué tipo de objeto estamos creando, pero sin acceder a los constructores originales. Todo el trabajo que lleva el construir el objeto se delega a estas funciones de fábrica.
Conclusión
Los patrones de diseño son técnicas útiles para programas complejos y que necesitan el uso consecutivo de herencia. Al principio ha de parecer algo no muy relevante o necesario, pero sin duda evita esas molestias de importación, y más cuando se trabaja con códigos ajenos y se tiene que investigar el uso adecuado de sus constructores.
Yo lo veo como una forma de organización que debería ser implementado por muchas personas. Si esto se practica de forma consecutiva, entonces nuestros códigos serían sumamente prácticos, se mantendría el encapsulamiento de nuestras clases y nos facilita la portabilidad de código.