Patrones de Fabricación: Fábricas de Objetos

Por León Welicki

Contenido

 1. Introducción
     1.1. Fábricas
     1.2. Factory Method vs. Creation Methods
     1.3. Relación entre los Patrones de Factoría
 2. Factory Method
     2.1. Definición del Patrón
     2.2. Breve Discusión
     2.3. Ejemplo "No Software"
     2.4. Ejemplos en .net Framework
     2.5. Ejemplos de Código
         2.5.1. Variaciones de Factory Method
             2.5.1.1. Creador es una Clase Abstracta o Interface
             2.5.1.2. Creador es una Clase Concreta con Implementación Predeterminada
             2.5.1.3. Métodos de Fabricación Parametrizados
             2.5.1.4. Lazy Initialization
             2.5.1.5. Combinación de Factory Method y Template Method
 3. Abstract Factory
     3.1. Definición del Patrón
     3.2. Breve Discusión
     3.3. Factory Method y Abstract Factory
     3.4. Ejemplo "No Software"
     3.5. Ejemplos en .net
     3.6. Ejemplo de Código
 4. Factory Pattern (Simple Factory)
     4.1. Ejemplos de Código
     4.2. ¿Por qué estas Clases no se engloban en los Patrones Anteriores?
         4.2.1. Simple Factory y Factory Method
         4.2.2. Simple Factory y Abstract Factory
 5. Conclusión: Flexibilidad y Complejidad
 Referencias y Bibliografía

1. Introducción

En este artículo analizaremos los patrones de fabricación más conocidos. Llamamos patrones de fabricación a aquellos patrones que involucran algún tipo de factoría o fábrica (factory, en inglés) de objetos. Estos patrones entran en la categoría de patrones de creación [GoF95], la cual comparten con otros patrones tales como el Singleton, Builder y Prototype [GoF95].

Los objetos de fabricación (fábricas) tienen la responsabilidad de crear instancias de objetos de otras clases. Tienen además la responsabilidad y el conocimiento necesario para encapsular la forma en que se crean determinados tipos de objetos en una aplicación.

Existen diferentes patrones de fabricación. En este artículo, trataremos Abstract Factory, Factory Method, y Simple Factory. Los dos primeros están incluidos en el catálogo del GoF (presentado y analizado en la entrega anterior) y serán tratados en mayor profundidad.

1.1. Fábricas

Para comenzar con nuestro estudio debemos definir claramente qué es una factoría o fábrica. De la misma forma que sucede con muchos conceptos en la Ingeniería del Software, existen varias definiciones para este término (en la entrega anterior hemos experimentado esta situación para el concepto de patrón); por tanto, es fundamental clarificar este concepto para sentar las bases de nuestro estudio posterior.

El término factory (o fábrica) adolece de ser sobreutilizado e impreciso. Mucha gente se refiere a factory para indicar una implementación de Factory Method o de Abstract Factory (patrones definidos en el libro del GoF). Otros sin embargo, se refieren a un objeto que crea instancias de otros, aunque no sigue las reglas de los patrones mencionados anteriormente.

La falta de consenso sobre el término dificulta la comunicación que, como ya hemos visto anteriormente, es uno de los objetivos de los patrones (poder referirse en forma unívoca a una abstracción de mayor nivel que la clase individual, incrementando así el vocabulario de los ingenieros de software). Recordemos entonces una importante frase de la entrega anterior:

Los Patrones permiten establecer un vocabulario común de diseño, cambiando el nivel de abstracción a colaboraciones entre clases y permitiendo comunicar experiencia sobre dichos problemas y soluciones. Son también un gran mecanismo de comunicación para transmitir la experiencia de los ingenieros y diseñadores experimentados a los más nóveles, convirtiéndose en unas de las vías para la gestión del conocimiento.

Por tanto, es muy importante establecer un significado claro para este término, a los efectos de construir este vocabulario común.

Llamaremos fábrica, factoría o factory a una clase que implemente uno o más métodos de creación, que son los métodos que se encargan de crear instancias de objetos (estas instancias pueden ser de esta misma clase o de otras). Esta clase tiene entre sus responsabilidades la creación de instancias de objetos, pero puede tener también otras responsabilidades adicionales. Los métodos de creación pueden ser estáticos.

Existen diferentes "tipos" de fábricas. A continuación enumeraremos cada una de ellos, a los efectos de dejar bien en claro sus significados:

Simple Factory

Clase utilizada para crear nuevas instancias de objetos.

Factory Method

Define una interfaz para crear objetos pero deja que sean las subclases las que deciden qué clases instanciar.

Abstract Factory

Proporciona una interfaz para crear familias de objetos relacionados o que dependen entre sí, sin especificar sus clases concretas.

En la Figura 1 se muestra los diferentes tipos de factorías con las que trabajaremos en este artículo. En cada caso, cada cuadro representa una clase y cada línea es un método en esa clase:

Bb972258.art251-img01-425x400(es-es,MSDN.10).jpg
Figura 1: Tipos de fábricas, inspirado en [Kerievsky04]. Volver al texto.

A lo largo de este artículo estudiaremos en mayor profundidad cada uno de estos patrones.

1.2 Factory Method vs. Creation Methods

¿Cómo llamar a un método que crea instancias de otras clases? Muchas veces se lo llama Factory Method, aunque esto puede prestarse a malos entendidos, dado que puede confundirse con el patrón del GoF homónimo. Por lo tanto, llamaremos a los métodos (o funciones) encargadas de crear instancias de otras clases Creation Methods (métodos de creación) a los efectos de establecer claramente esta diferenciación.

En la Figura 2 se muestra la relación entre los conceptos Factory Method y Creation Method:

Bb972258.art251-img02-563x287(es-es,MSDN.10).jpg
Figura 2: Relación entre Factory Method y Factory Function; Factory Method contiene varios Creation Methods (métodos de creación). Volver al texto.

1.3 Relación entre los Patrones de Factoría

Los patrones no existen aislados el uno del otro, sino más bien dentro del contexto de un lenguaje o sistema de patrones. Por consiguiente existen relaciones entre ellos que pueden determinar cuándo, cómo y por qué utilizar uno u otro. Los patrones de fabricación no están exentos de esto.

En el libro del GoF por ejemplo, se indica que el patrón Abstract Factory se implementa utilizando Factory Method. A su vez, ambos patrones están relacionados con otros patrones del catálogo. En la Figura 3 se muestran las relaciones entre patrones de creación que existen dentro del catálogo del GoF [GoF95]:

Bb972258.art251-img03-506x197(es-es,MSDN.10).jpg
Figura 3: Relaciones entre los patrones de creación del catálogo del GoF (tomada de [GoF95]). Hemos marcado con un borde rojo los patrones que tratamos en este artículo (Abstract Factory y Factory Method). Volver al texto.

En la Figura 4 se representa la forma en que están relacionados todos los conceptos referentes a las fábricas estudiados hasta ahora:

Bb972258.art251-img04-564x277(es-es,MSDN.10).jpg
Figura 4: Diagrama basado en UML que muestra las relaciones entre los distintos patrones tratados en este artículo. Volver al texto.

En la vida real, los patrones no están segregados por líneas absolutas. A menudo, una clase puede utilizar uno o más patrones, haciendo difuso el límite entre ambos. Adicionalmente, se puede comenzar utilizando un patrón sencillo y evolucionar hacia otro más complejo en función de las necesidades de nuestra aplicación

 

2. Factory Method

Factory Method define una interfaz para crear objetos, pero deja que sean las subclases quienes decidan qué clases instanciar; permite que una clase delegue en sus subclases la creación de objetos.

2.1 Definición del Patrón

A continuación presentaremos una versión reducida de la plantilla de este patrón. Para obtener la versión completa, consulta el libro del GoF:

Problema

Una clase necesita instanciar otra clase derivada de una tercera clase, pero no sabe cuál. Factory Method permite a la clase derivada tomar esta decisión.

Solución

Una clase derivada toma la decisión sobre qué clase instanciar y cómo instanciarla (Ver Figura 5):

Bb972258.art251-img05-527x179(es-es,MSDN.10).gif
Figura 5: Diagrama OMT de Factory Method, tomado del libro del GoF. Volver al texto.

Participantes

Producto

Define la interfaz de los objetos que crea el método de fabricación.

ProductoConcreto

Implementa la interfaz Producto.

Creador

Declara el método de fabricación, el cual devuelve un objeto del tipo Producto. También puede definir una implementación predeterminada del método de fabricación que devuelve un objeto ProductoConcreto. Puede llamar al método de fabricación para crear un objeto Producto.

CreadorConcreto

Redefine el método de fabricación para devolver una instancia de ProductoConcreto.

Aplicabilidad

Usar cuando:

  • Una clase no puede prever la clase de objetos que debe crear.

  • Una clase quiere que sean sus subclases quienes especifiquen los objetos que ésta crea.

  • Las clases delegan la responsabilidad en una de entre varias clases auxiliares, y queremos localizar concretamente en qué subclase de auxiliar se delega.

Consecuencias

Proporciona enganches para las subclases. Crear objetos dentro de una clase con un método de fabricación es siempre más flexible que hacerlo directamente. Conecta jerarquías de clases paralelas.

Resumen 1: Vista simplificada y resumida del patrón Factory Method, tomado de [GoF95] y [DPE01]

2.2 Breve Discusión

El patrón Factory Method permite escribir aplicaciones que son más flexibles respecto de los tipos a utilizar difiriendo la creación de las instancias en el sistema a subclases que pueden ser extendidas a medida que evoluciona el sistema. Permite también encapsular el conocimiento referente a la creación de objetos. Factory Method hace también que el diseño sea más adaptable a cambio de sólo un poco más de complejidad. Se utiliza frecuentemente con Template Method. En la sección de ejemplos de código de este patrón presentaremos una implementación de este caso.

Uno de los principales inconvenientes que puede presentar este patrón es que puede requerir crear una nueva clase simplemente para cambiar la clase de Producto.

2.3 Ejemplo "No Software"

En [Duell97] se presenta el siguiente ejemplo:

Las prensas de moldeado a inyección sirven para explicar este patrón. Los fabricantes de juguetes de plástico procesan plástico en polvo para moldeado, e inyectan el plástico en moldes con las formas deseadas. La clase de un juguete (auto, figura, etc.) es determinada por el molde.

En la Figura 6 se muestra un diagrama UML de este ejemplo:

Bb972258.art251-img06-260x260(es-es,MSDN.10).jpg
Figura 6: Ejemplo del mundo real del patrón Factory Method, tomado de [Duell97]. Volver al texto.

2.4 Ejemplos en .net Framework

En .net podemos encontrar varias implementaciones de este patrón. A modo de ejemplo, hemos tomado una de ASP .net 1.1, concretamente, la implementación del gestor de manejadores (handlers).

En la Figura 7 se muestra el diagrama del GoF actualizado con objetos del Framework .net; en este caso los del ejemplo seleccionado:

Bb972258.art251-img07-562x287(es-es,MSDN.10).jpg
Figura 7: Ejemplo de implementación del patrón Factory Method en ASP .net. Volver al texto.

2.5 Ejemplos de Código

Existe un número de variantes para este patrón. En esta sección enumeraremos y explicaremos las principales y ofreceremos ejemplos en C# para cada caso.

Para los ejemplos, utilizaremos como clases Producto un modelo de objetos muy sencillo de una tienda de mascotas. En este caso, Mascota hace las veces del participante Producto; y Perro, Gato y Víbora son instancias de Producto Concreto (Ver Figura 8):

Bb972258.art251-img08-457x133(es-es,MSDN.10).gif
Figura 8: Modelo de objetos Producto utilizados en los ejemplos de implementación del patrón Factory Method. Volver al texto.

2.5.1 Variaciones de Factory Method

Si bien existen diversas variaciones en la implementación de este patrón, los dos principales casos son los que se indican a continuación:

  • Creador es abstracto y no provee una implementación para el método de creación que declara.

  • Creador es una clase concreta y provee una implementación predeterminada para el método de creación que declara.

En las siguientes secciones presentaremos las distintas opciones de implementación de este patrón y para cada una de ellas ofreceremos un ejemplo en C#.

2.5.1.1 Creador es una Clase Abstracta o Interfaz

En este caso, el creador es una clase abstracta o interfaz (siempre que sea posible, es más recomendable utilizar una interfaz) y no provee una implementación predeterminada. Por lo tanto, son las clases derivadas las que tienen la responsabilidad sobre la implementación de la función de creación.

En el ejemplo que se muestra a continuación existe un creador que crea instancias de Perro y otro que crea instancias de Gato, ambos basados en la misma interfaz:

/// <summary>
/// Creador sin implementación. Puede ser una interfase 
/// o una clase abstracta, donde los métodos de creación
/// no tienen implementación
/// </summary>
public interfase ICreador
{
   /// <summary>
   /// Método de creación
   /// </summary>
   /// <returns>Instancia de una mascota</returns>
   Mascota Crear();
} 
 
/// <summary>
/// Creador Concreto. Implementa la interfase de creación
/// </summary>
public class CreadorConcreto: ICreador
{
   /// <summary>
   /// Método de creación
   /// </summary>
   /// <returns>Instancia de una mascota</returns>
   public Mascota Crear()
   {
      return new Perro();
   }
}
 
/// <summary>
/// Otra instancia de Creador Concreto. 
/// Implementa la interfase de creación
/// </summary>
public class OtroCreadorConcreto: ICreador
{
   /// <summary>
   /// Método de creación. En este caso, retorna
   /// un una instancia de una mascota de la clase Gato
   /// </summary>
   /// <returns>Instancia de una mascota</returns>
   public Mascota Crear()
   {
      return new Gato();
   }
}

Código 1 - Ejemplo de Factory Method donde el Creador es una interfaz. En este ejemplo de código, existen 2 creadores concretos que son las clases que implementan la interfase ICreador. Cada creador crea un subtipo diferente de Mascota, por ejemplo, Perro en el primer caso y Gato en el segundo.

2.5.1.2 Creador es una Clase Concreta con Implementación Predeterminada

En este caso, la clase base provee una implementación predeterminada de la función de creación. Por lo tanto, las clases hijas pueden re-implementarla o utilizar la implementación provista por la clase base.

Para lograr este comportamiento, la función de creación en la clase base debe marcarse como virtual (esto significa que puede ser redefinida por las clases derivadas).

En el ejemplo a continuación veremos como la clase base crea instancias de objetos de tipo Perro y la subclase, en cambio, crea instancias de Gato (redefiniendo la implementación del método de creación del tipo base):

/// <summary>
/// Creador. Esta es la clase base del Factory Method.
/// Provee una implementación default del método de creación,
/// que puede ser redefinida por los métodos hijos
/// </summary>
public class Creador
{
   /// <summary>
   /// Metodo de creación
   /// </summary>
   /// <returns>Instancia de una mascota</returns>
   public virtual Mascota Crear()
   {
      return new Perro();
   }
}
 
/// <summary>
/// Creador Concreto. Redefine el método de creación
/// definido en Creador, cambiando el "ProductoConcreto"
/// que se retorna
/// </summary>
public class CreadorConcreto: Creador
{
   /// <summary>
   /// Metodo de creación
   /// </summary>
   /// <returns>Instancia de una mascota</returns>
   public override Mascota Crear()
   {
      return new Gato();
   }
}

Código 2 - Ejemplo de Factory Method donde Creador es una clase concreta y tiene implementación predeterminada. Creador provee una implementación predeterminada que es redefinida por los creadores concretos. En el ejemplo, CreadorConcreto redefine el método Crear, retornando una instancia de otra subclase de Mascota (concretamente Gato en lugar de Perro).

2.5.1.3 Métodos de Fabricación Parametrizados

Otra variación del patrón permite que la función de creación cree múltiples instancias de Producto. La función recibe como entrada un parámetro que identifica el tipo de objeto a crear. Todos los objetos creados por esta función deben ser subclases de Producto.

En el ejemplo que se muestra a continuación, se crea una mascota en función de un parámetro con el nombre del tipo de Mascota. En este mismo ejemplo se crea una subclase de Creador (un creador concreto) muy quisquilloso: sólo puede crear instancias de mascotas de tipo Perro. En los demás casos dispara una excepción, explicando el motivo por el cual no ha podido crear la instancia:

/// <summary>
/// Creador. Esta es la clase base del Factory Method. 
/// Provee una implementación default del método de creación,
/// que puede ser redefinida por los métodos hijos. El método
/// de creación acepta parámetros, permitiendo crear diferentes 
/// instancias de mascotas
/// </summary>
public class Creador
{
   /// <summary>
   /// Función de creación de ordenadores. Recibe el tipo 
   /// de ordenador a crear y retorna una instancia valida
   /// </summary>
   /// <remarks>
   /// Dado que la función es virtual, puede ser modificada
   /// sus las clases hijas
   /// </remarks>
   /// <param name="tipo">Tipo de ordenador (producto) a crear</param>
   /// <returns>Instancia valida de ordenador (producto)</returns>
   public virtual Mascota Crear(string tipo)
   {
      switch (tipo.ToLower())
      {
         case "perro":
            return new Perro();
         case "gato":
            return new Gato();
         case "vibora":
            return new Vibora();
         default:
            throw new ArgumentException("Tipo de mascota desconocido");
      }
   }
}
 
/// <summary>
/// Creador Concreto. Redefine el método de creación definido en Creador,
/// cambiando el "ProductoConcreto" que se retorna. En este caso,
/// sólo permite crear mascotas del tipo "Perro". En los demás casos,
/// dispara una excepción
/// </summary>
public class CreadorConcreto: CreadorParametrizado
{
   /// <summary>
   /// Función de creación de ordenadores. Solo permite
   /// crear Mascotas de tipo Perro.
   /// </summary>
   /// <remarks>
   /// Esta función redefine a la función anterior, cambiando
   /// el comportamiento de creación en función del método
   /// </remarks>
   /// <param name="tipo">Tipo de ordenador (producto) a crear</param>
   /// <returns>Instancia valida de ordenador (producto)</returns>
   public override Mascota Crear(string tipo)
   {
      switch (tipo.ToLower())
      {
         case "perro":
            return new Perro();
         case "gato":
            throw new ArgumentException("Soy alérgico a los gatos.");
         case "víbora":
            throw new ArgumentException("No se puede tener una víbora como mascota.");
         default:
            throw new ArgumentException("Tipo de mascota desconocido");
      }
   }
}

Código 3 - Ejemplo de Factory Method con el método de creación parametrizado. Nota que el método de creación además de recibir un parámetro está marcado como virtual, para poder ser redefinido en las clases hijas.

2.5.1.4 Lazy Initialization

En algunos casos, por ejemplo cuando la creación de la instancia tiene un coste elevado, se puede mantener una referencia a una instancia válida, la cual sólo es creada la primera vez que se solicita. Por lo tanto, sólo incurrimos en el coste de creación una única vez. A continuación se muestra un ejemplo de cómo implementar esta técnica:

/// <summary>
/// Creador. Esta es la clase base del patrón Factory Method. 
/// Provee una implementación default del método de creación,
/// que puede ser redefinida por los métodos hijos. El método
/// de creacion utiliza Lazy Instantiation.
/// </summary>
public class Creador
{
   // instancia de "Producto"
   private Mascota producto;
 
/// <summary>
   /// Método de creación.
   /// </summary>
   /// <remarks>
   /// Es un "TemplateMethod". Para modificar la clase a instanciar, 
   /// se debe modificar en las clases hijas el método CrearOrdenador
   /// </remarks>
   /// <returns>Instancia de Mascota</returns>
   public Mascota Crear()
   {
      /// si no se ha creado el producto, lo creamos
      if (this.producto == null)
         this.CrearMascota();
 
/// retorna la instancia del producto
      return this.producto;
   }
 
/// <summary>
   /// Método real de fabricación. 
   /// </summary>
   /// <remarks>
   /// Este método que crea la instancia. Es el que debe ser redefinido
   /// en las clases hijas para modificar la clase del objeto a crear
   /// </remarks>
   protected virtual void CrearMascota()
   {
      this.producto = new Perro();
   }
}

Código 4 - Ejemplo de Factory Method utilizando Lazy Load.

Otro aspecto interesante a notar es la utilización del patrón Template Method [GoF95] para seleccionar el tipo de la instancia a crear. La función CrearMascota funciona como Template Method: este método tiene el comportamiento variable, mientras que crear tiene el comportamiento que no varía. Por lo tanto, si quisiéramos crear una subclase para crear instancias de otro tipo de Mascota (por ejemplo Gato), sólo tendríamos que redefinir el método CrearMascota.

2.5.1.5 Combinación de Factory Method y Template Method

El patrón Template Method "define en una operación el esqueleto de un algoritmo, delegando en las subclases algunos de sus pasos. Permite que las subclases redefinan ciertos pasos de un algoritmo sin cambiar su estructura" [GoF95]. En el ejemplo que se muestra a continuación mostramos cómo combinar estos dos patrones.

Para demostrar esto con un ejemplo, implementaremos un cuidador de mascotas. El cuidador tiene la responsabilidad de alimentar y pasear a nuestras mascotas. Su comportamiento puede describirse de la siguiente forma:

  1. Obtiene una mascota para cuidar.

  2. La alimenta.

  3. Si tiene 2 ó más patas, la lleva a pasear.

Las clases Cuidador y CuidadorDePerros combinan a los patrones Factory Method y Template Method. La clase Cuidador cumple los roles de Creador en Factory Method y Abstract Class en Template Method.

El método Cuidar tiene el comportamiento fijo y no puede ser redefinido en las clases hijas. Recordando la definición del patrón, este método contiene "la estructura del algoritmo". El método abstracto CrearMascota, en cambio, es un método de creación (en Factory Method) y una operación primitiva (en Template Method). En ambos casos este método debe ser redefinido en las clases derivadas.

La clase CuidadorDePerros muestra un ejemplo de cómo puede crearse un cuidador que sepa tratar con perros:

/// <summary>
/// Esta es la clase que implementa Template Method
/// y Factory Method. En el caso de Factory Method,
/// hemos implementado la opción donde el creador
/// es una clase abstracta
/// </summary>
public abstract class Cuidador
{
   /// <summary>
   /// Este es el template method. Este método es
   /// el que tiene la lógica y no se redefine en 
   /// las clases hijas
   /// </summary>
   public void Cuidar()
   {
      /// obtenemos una instancia de una mascota
      Mascota mascota = this.CrearMascota();
 
      /// alimentamos a la mascota
      mascota.Alimentar();
 
      /// si la mascota tiene mas de 2 patas, la llevamos a pasear
      if (mascota.CantidadPatas > 1)
         mascota.Pasear();
   }
 
   /// <summary>
   /// Este es el método de creación (para el Factory Method)
   /// y la Operación Primitiva que debe ser
   /// redefinida por el usuario (para el Template Method)
   /// </summary>
   /// <returns>Instancia de Mascota</returns>
   public abstract Mascota CrearMascota();
}
 
/// <summary>
/// Creador Concreto. Solo redefine la operación de creación, pero hereda
/// el comportamiento fijo de CreadorTemplateMethod
/// </summary>
public class CuidadorDePerro: CreadorTemplateMethod
{
   /// <summary>
   /// Método de creación, redefinido de la clase base
   /// </summary>
   /// <returns>Instancia de Mascota</returns>
   public override Mascota CrearMascota()
   {
      return new Perro();
   }
}

Código 5 - Ejemplo de combinación de Factory Method y Template Method.

Es importante destacar que este código es un ejemplo ilustrativo y por lo tanto puede carecer de sentido en el mundo real, dado que el cuidador cuida siempre "nuevas instancias de perros". Este ejemplo podría mejorarse creando una subclase de cuidador que obtenga la instancia de Mascota a partir de una serie de criterios arbitrarios. Las técnicas que estamos utilizando permiten probar nuevas alternativas sin afectar al código existente (por ejemplo, podríamos crear una subclase de cuidador, probarla y mejorarla sin necesidad de modificar a las clases CuidadorDePerros o Cuidador).

 

3. Abstract Factory

El patrón Abstract Factory proporciona una interfaz para crear familias de objetos relacionados o que dependen entre sí, sin especificar sus clases concretas.

3.1 Definición del Patrón

Intención

Proporciona una interfaz para crear familias de objetos relacionados o que dependen entre sí, sin especificar sus clases concretas.

Problema

Se necesita instanciar familias de objetos.

Solución

Coordinar la creación de familias de objetos. Establecer una forma para quitar las reglas de cómo realizar la instanciación fuera del objeto que está usando los objetos a crear. (Ver Figura 9):

Bb972258.art251-img09-568x253(es-es,MSDN.10).gif
Figura 9: Diagrama OMT de Factory Method, tomado del libro del GoF. Volver al texto.

Participantes

FabricaAbstracta

Declara una interfaz para operaciones que crean objetos producto abstractos.

FabricaConcreta

Implementa las operaciones para crear objetos producto concretos.

ProductoAbstracto

Declara una interfaz para un tipo de objeto producto.

ProductoConcreto

Define un objeto producto para que sea creado por la fábrica correspondiente. Implementa la interfase ProductoAbstracto.

Cliente

Sólo usa interfaces declaradas por las clases FabricaAbstracta y ProductoAbstracto.

Aplicabilidad

Usar cuando:

  • Un sistema debe ser independiente de cómo se crean, componen y representan sus productos.

  • Un sistema debe ser configurado con una familia de productos entre varias.

  • Una familia de objetos producto relacionados está diseñada para ser usada conjuntamente y es necesario hacer cumplir esa restricción.

  • Se quiere proporcionar una biblioteca de clases de productos y sólo se quiere revelar sus interfaces, no sus implementaciones.

Consecuencias

  • Aísla las clases concretas.

  • Facilita el intercambio de familias de productos.

  • Promueve la consistencia entre productos.

  • Desventaja: Es difícil dar cabida a nuevos tipos de productos.

Resumen 2 - Vista simplificada y resumida del patrón Abstract Factory, tomado de [GoF95] y [DPE01].

3.2 Breve Discusión

Abstract Factory puede ser utilizado para desarrollar frameworks y sistemas que pueden ser configurados con una de múltiples familias de productos.

Provee un nivel adicional de indirección que abstrae la creación de familias de objetos relacionados o dependientes sin especificar sus clases concretas. El objeto "fábrica" tiene la responsabilidad de proveer la funcionalidad y el protocolo para la creación de la familia completa. Los clientes nunca deben crear objetos directamente, sino a través de la factoría. Por consiguiente, es fácil cambiar las familias de productos que se utilizan, porque el tipo específico de cada instancia aparece sólo una vez en la aplicación: en el método de creación donde se crea la instancia.

Como hemos dicho anteriormente, este patrón es utilizado cuando se desean crear familias de productos, pero... ¿Qué queremos decir con familias de productos? Imaginemos que tenemos una aplicación y queremos que pueda mostrarse en múltiples sistemas de ventanas como por ejemplo Windows 9x, 2000, XP, Mac OS y X-Windows. Cuando creamos los diferentes controles de usuario, es muy importante que se creen en forma consistente. Por ejemplo, en una ventana no queremos que se mezclen botones tipo Windows 95 con barras de desplazamiento de estilo MacOS. Por esto, debemos asegurarnos que todos los objetos de interfaz de usuario que creemos pertenezcan a la misma familia (Windows 95, 2000, XP MacOS, etc.). El patrón Abstract Factory nos ayuda a resolver este problema.

3.3 Factory Method y Abstract Factory

Abstract Factory generalmente se implementa utilizando Factory Method y por tanto provee al menos toda la flexibilidad de éste. La diferencia principal entre ambos es que el primero trata con familias de productos, mientras que el otro se preocupa por un único producto.

Abstract Factory se encuentra a un nivel de abstracción mayor que Factory Method.

Los diseños que usan Abstract Factory son más flexibles que los que utilizan Factory Method, pero son también más complejos.

Otra diferencia notable es el ámbito de ambos patrones: Factory Method es un patrón de clase, mientras que Abstract Factory es un patrón de objeto. Los patrones de clase se refieren a las relaciones entre las clases (estáticas, en tiempo de compilación) y sus subclases mientras que los de objetos tratan sobre relaciones entre instancias (dinámicas, en tiempo de ejecución). Los patrones de objetos suelen ser preferibles a los de clases, ya que se basan en el principio fundamental de usar composición en lugar de la herencia y en la delegación. La mayoría de los patrones del GoF tienen ámbito de objeto. Para acceder a una discusión más detallada sobre este tema, ver [GoF95].

3.4 Ejemplo "No Software"

Este patrón se encuentra en el equipamiento de cuño de metal utilizado en las fábricas de automóviles japonesas. El equipamiento de cuño es un Abstract Factory que crea partes de un automóvil. La misma maquinaria se utiliza para estampar la puerta derecha e izquierda, defensa delantera y trasera, etc., para diferentes modelos de autos. Mediante el uso de rodillos para cambiar los fines de estampado, las "clases concretas" producidas por la maquinaria pueden cambiarse en 3 minutos [Duell97].

En la Figura 10, que se muestra a continuación, vemos un ejemplo de esta situación (utilizando una notación basada en UML):

Bb972258.art251-img10-452x404(es-es,MSDN.10).gif
Figura 10: Ejemplo del mundo real del patrón "Abstract Factory", tomado de . Volver al texto.

3.5 Ejemplos en .net

El patrón Abstract Factory se utiliza en forma intensiva en ADO .net. En la Figura 11 se muestra cómo ha sido implementado este patrón en los objetos conexión de ADO .net:

Bb972258.art251-img11-565x289(es-es,MSDN.10).jpg
Figura 11: Implementación de Abstract Factory en las conexiones ADO .net. Las líneas discontinuas representan la relación de creación (la notación en este caso es OMT , la misma que se usa en el libro del GoF). Volver al texto.

En la figura anterior vemos cómo mediante la utilización del patrón nos aseguramos que los objetos que se creen a partir de un tipo de conexión (Transacción o Command) sean de la misma familia que la del objeto conexión (SqlClient, OracleClient, etc.)

3.6 Ejemplo de Código

Para mostrar el uso de este patrón, codificaremos en C# el ejemplo que se presenta en la sección Motivación de la plantilla de este patrón en el libro del GoF.

En este caso, se busca que todos los objetos gráficos que se creen en una aplicación que soporte múltiples sistemas de visualización pertenezcan a la misma familia. De este manera, si creamos una ventana para ser mostrada en Windows, los controles de usuario (scrollbar, textbox, etc) que utilizaremos en ella deben ser controles para Windows. Lo mismo debería suceder si estuviéramos presentando la aplicación en un Mac.

En la figura Figura 12 se muestra el diagrama OMT del ejemplo:

Bb972258.art251-img12-569x277(es-es,MSDN.10).jpg
Figura 12: Diagrama OMT del ejemplo de Abstract Factory para regir la creación de objetos gráficos en un sistema que soporta visualización en múltiples sistemas de ventanas. Este diagrama ha sido tomado de la sección Motivación de este patrón en el libro del GoF. Volver al texto.

A continuación se muestra el código C# para el diagrama anterior:

/// <summary>
/// Abstract Factory. En este caso, la hemos implementado usando
/// una interfase, aunque también puede ser una clase abstracta
/// </summary>
public interface IWidgetFactory
{
   Window CreateWindow();
   Scrollbar CreateScrollbar();
}
 
/// <summary>
/// Concrete Factory (Fabrica Concreta)
/// </summary>
public class WindowsWidgetFactory: IWidgetFactory
{
   public Window CreateWindow()
   {
      return new WindowsWindow();
   }
 
public Scrollbar CreateScrollbar()
   {
      return new WindowsScrollbar();
   }
}
 
/// <summary>
/// Concrete Factory (Fabrica Concreta)
/// </summary>
public class MacWidgetFactory: IWidgetFactory
{
   public Window CreateWindow()
   {
      return new MacWindow();
   }
 
public Scrollbar CreateScrollbar()
   {
      return new MacScrollbar();
   }
}
 
/// <summary>
/// Producto
/// </summary>
public abstract class Window
{
   public abstract void Render();
}
 
/// <summary>
/// Producto
/// </summary>
public abstract class Scrollbar
{
   public abstract void Render();
}
 
/// <summary>
/// Producto Concreto (Scrollbar para Windows)
/// </summary>
public class WindowsScrollbar: Scrollbar
{
   public override void Render()
   {
      Console.WriteLine("Pintando Scrollbar de Windows...");
   }
}
 
/// <summary>
/// Producto Concreto (Ventana para Windows)
/// </summary>
public class WindowsWindow: Window
{
   public override void Render()
   {
      Console.WriteLine("Pintando Ventana de Windows...");
   }
}
 
/// <summary>
/// Producto Concreto (Scrollbar para Mac)
/// </summary>
public class MacScrollbar: Scrollbar
{
   public override void Render()
   {
      Console.WriteLine("Pintando Scrollbar de Mac...");
   }
}
 
/// <summary>
/// Producto Concreto (Ventana para Mac)
/// </summary>
public class MacWindow: Window
{
   public override void Render()
   {
      Console.WriteLine("Pintando Ventana de Mac...");
   }
}
 
class TestClient
{
   /// <summary>
   /// Punto de entrada principal de la aplicación.
   /// </summary>
   [STAThread]
   static void Main(string[] args)
   {
      /// Creo los objetos para windows
      IWidgetFactory factory = new WindowsWidgetFactory();
      Scrollbar scrollbar = factory.CreateScrollbar();
      Window window = factory.CreateWindow();
      window.Render();
      scrollbar.Render();
 
      /// Ahora, lo mismo pero para Mac. 
      /// Al cambiar el tipo del factory, todos los objetos que 
      /// se crean mediante ella son de la misma familia
      factory = new MacWidgetFactory();
      scrollbar = factory.CreateScrollbar();
      window = factory.CreateWindow();
      window.Render();
      scrollbar.Render();
   }
}

Código 6 - Ejemplo de implementación de Abstract Factory. En el ejemplo hay también un cliente de prueba.

 

4. Factory Pattern (Simple Factory)

Muchas veces, cuando la gente habla de factory pattern, se refiere a uno de los dos patrones que hemos estudiado anteriormente. Sin embargo, hay casos que no son cubiertos por estos patrones como por ejemplo, clases con métodos estáticos de fabricación o fábricas concretas que tienen implementación, pero sus métodos no son redefinibles. En estos casos, estamos ante una implementación del patrón Simple Factory.

Hemos dejado este tipo de factorías para el final, dado que son más fáciles de entender y analizar que los otros patrones presentados anteriormente en este artículo.

En este caso, estamos ante tipos que tienen la responsabilidad de crear instancias de objetos de otras clases, pero que no cumplen con las premisas establecidas en los patrones anteriores.

4.1 Ejemplos de Código

/// <summary>
/// Simple Factory
/// Ejemplo con método de creación estático
/// </summary>
public class CreadorDePerros
{
   public static Mascota Create ()
   
   return new Perro();
   }
} 
/// <summary>
/// Ejemplo de Simple Factory
/// </summary>
public class CreadorDeGatos
{
   public Mascota Create()
   {
   return new Gato();
   }
}

Código 7 - Ejemplo de implementación de variaciones de Simple Factory. En el primer caso, el método de creación es estático. Nota que no pueden redefinirse los métodos de fabricación en ninguna de las dos clases.

4.2 ¿Por qué estas Clases no se engloban en los Patrones Anteriores?

Quizás la primer pregunta que puedes hacerte al ver el Código 7 es: ¿Por qué estas clases no se corresponden con los patrones anteriores? En este apartado intentaremos explicarlo ...

4.2.1 Simple Factory y Factory Method

Comencemos con Factory Method. Para ello es fundamental recordar la intención de este patrón: Factory Method define una interfaz para crear objetos, pero deja que sean las subclases las que decidan qué clases instanciar. Permite que una clase delegue en sus subclases la creación de objetos.

En los ejemplos presentados en el Código 7, las clases no exponen una interfaz que pueda ser redefinida por clases que deriven de ésta. Por tanto, no pueden dejar que sean las subclases las que decidan qué clases instanciar ni delegar en las subclases la creación de objetos. En ambos casos esto se debe a que el comportamiento está definido en cada clase y no puede ser redefinido en sus clases derivadas.

4.2.2 Simple Factory y Abstract Factory

Continuemos con Abstract Factory. De la misma forma que en el caso anterior, recordaremos la intención de este patrón: Proporciona una interfaz para crear familias de objetos relacionados o que dependen entre sí, sin especificar sus clases concretas.

En los ejemplos presentados en el Código 7, no exponen una interfaz para crear familias de objetos relacionados, dado que solo crean un único producto y tampoco pueden ser redefinidos por las clases derivadas.

Respecto a la restricción de las familias, podrías estar pensando como contra-argumento el siguiente ejemplo de código:

/// <summary>
/// Ejemplo de Simple Factory
/// </summary>
public class Creador
{
public static IWindow CreateWindow()
{
return new WindowsWindow();
}
 
public static IScrollbar CreateScrollbar()
{
return new WindowsScrollbar();
}
}

Código 8 - Ejemplo de implementación de creación de familias de productos con Simple Factory.

En este nuevo ejemplo, nuestro creador sí crea familias de productos, aunque tiene los siguientes inconvenientes:

  1. No asegura ningún tipo de consistencia.

  2. No declara una interfaz de creación de familias de productos que pueda ser redefinida por las clases derivadas.

El punto 2 quizás sea el más fácil de ver, aunque el punto 1 puede generar dudas ... Por eso, discutiremos brevemente el problema y lo demostraremos con un nuevo bloque de código: al no tener una restricción respecto a la interfaz de los creadores de familias, estos pueden mezclarse, produciendo inconsistencias como las que se muestran en el bloque de código:

/// <summary>
/// Simple Factory para Widgets de Windows
/// </summary>
public class WindowsCreator
{
   public static IWindow CreateWindow()
   {
   return new WindowsWindow();
   }
 
public static IScrollbar CreateScrollbar()
   {
   return new WindowsScrollbar();
   }
}
 
/// <summary>
/// Simple Factory para Widgets de MAC
/// </summary>
public class MacCreator
{
   public static IWindow CreateWindow()
   {
      return new MacWindow();
   }
 
public static IScrollbar CreateScrollbar()
   {
      return new MacScrollbar();
   }
}
 
/// <summary>
/// Ejemplo de Simple Factory
/// </summary>
public class ClienteDePruebaConError
{
   public void Test()
   {
      IWindow window = CreadorWindows.CreateWindow();
      IScrollbar scrollbar = CreadorMac.CreateScrollbar();
   }
}

Código 9 - Ejemplo de inconsistencias al utilizar Simple Factory para crear familias de productos.

En el ejemplo presentado en el Código 9, se crean y combinan elementos de interfaz de usuario de diferentes familias. En el caso concreto de nuestro ejemplo, estamos creando una ventana de Windows y una Scrollbar de Mac. Por lo tanto, estamos violando el principio fundamental del patrón, que es proveer una interfaz consistente para la creación de familias de productos relacionados. La forma correcta de implementar este ejemplo utilizando el patrón Abstract Factory ha sido presentada previamente en el bloque de Código 6.

 

5. Conclusión: Flexibilidad y Complejidad

Los patrones de fabricación estudiados a lo largo de este artículo añaden flexibilidad a las aplicaciones, pero a expensas de mayor complejidad. Por lo tanto, es recomendable analizar bien el problema a resolver antes de incluirlas.

Para terminar, considero oportuno citar la siguiente frase sobre las fábricas de Robert Martin [Martin05]:

No las use por defecto, y no comience utilizándolas frente al primer indicio de que puedan serle útiles ... Aguarde a tener un problema concreto que las fábricas puedan resolver. Y en ese caso, no dude en utilizarlas.

 

Referencias y Bibliografía

[C204] C2 Wiki: Abstract Factory vs Factory Method.
http://c2.com/cgi/wiki?AbstractFactoryVsFactoryMethod

[Duell97] Duell, Michael: Non-software examples of software design patterns, Object Magazine, July 1997, pp54.

[Fowler99] Fowler, Martin: Refactoring: Improving the Design of Existing Code, Adisson Wesley, 1999.

[GoF95] Gamma E., Helm, R., Johnson, R., Vlissides J.: Design Patterns: Elements of Reusable Object Oriented Software, Addison Wesley, 1995.

[Kerievsky04] Kerievsky, Joshua: Refactoring to Patterns, Addison-Wesley, 2004.

[Martin05] Martin, Robert: Principles, Patterns, and Practices: The Factory Pattern.
http://today.java.net/pub/a/today/2005/03/09/factory.html

[PPR04] C2 WikiWikiWeb: Portland Pattern Repository <en línea>.
http://c2.com/ppr/

[DPE01] Shalloway, Alan; Trott James : Design Patterns Explained : A New perspective on Object Oriented Design, Pearson Education, 2001.

[Vlissides98] Vlissides, John: Pattern Hatching: Design Patterns Applied, Addison Wesley, 1998.

León Welicki es Profesor Asociado de Ingeniería Web en el Máster en Ingeniería del Software de la Universidad Pontificia de Salamanca, Madrid, España; donde actualmente está realizando el Doctorado en Ingeniería Informática, su tesis doctoral trata sobre las Arquitecturas de Software y Paradigmas No Convencionales para Ingeniería Web. Trabaja como Arquitecto de Software. Cuenta con más de 12 años de experiencia profesional en diversas áreas de la Ingeniería del Software.