Design Patterns - Abstract Factory

Propósito

El Abstract Factory define una interfaz para crear una familia de objetos relacionados o que dependen entre sí, sin especificar sus clases concretas.

Problema

La necesidad de crear familias de productos que, idealmente, deben ser usados juntos, desacoplando el código de creación del código de los productos.

Solución

La solución que propone el Abstract Factory es:

  • Definir una interfaz para la creación de cada familia de objetos: Crear una interfaz o método abstracto para la creación de familia de productos
  • Delegar la creación a las subclases, fábricas concretas de cada familia de productos: Cada familia tendrá su propia implementación de estas interfaces

Estructura

abstract factory

Participantes

  • FabricaAbstracta: Interfaz que declara un conjunto de métodos para crear cada uno de los productos abstractos.
  • FabricaConcreta: Implementa los métodos para crear los productos concretos.
  • ProductoAbstracto: Declara una interfaz para un tipo de objeto producto.
  • ProductoConcreto: Define un objeto producto que va a ser creado por la correspondiente fábrica concreta. Implementa la interfaz ProductoAbstracto.
  • Cliente: sólo usa interfaces declaradas por las clases FabricaAbstracta y ProductoAbstracto

Cuándo Usarlo

Este patrón es recomendable cuando:

  • Un sistema debe ser independiente de como se crean, componen y representan los productos.
  • Cuando un sistema debe ser configurado con una de múltiples familias de productos.

Ventajas

Consistencia de Productos: Asegura que los productos que se crean son compatibles entre sí.

Intercambiabilidad: Facilita el cambio entre diferentes familias de productos.

Aislamiento: Separa los detalles de implementación de los productos del código cliente.

Desventajas

Complejidad: Puede aumentar la complejidad del código debido a la gran cantidad de clases e interfaces involucradas.

Dificultad para agregar nueva familia de productos: La interfaz fábrica abstracta fija los tipos de productos que se pueden crear, por lo que agregar nuevas familias implica cambiar esta interfaz y todas las subclases.

Ejemplo: Módulo de creación de interfaces gráficas

Pensemos en un módulo de software de creación de interfaces gráficas.

Debe estar preparado para funcionar en múltiples sistemas operativos (Windows, MacOS, Linux).

Cada sistema operativo tiene su propia manera de dibujar elementos, como ventanas, botones, cuadros de texto, menúes.

Problema

Los clientes deben poder crear cada familia de elementos correspondientes en base al sistema operativo que estén usando, desacoplando la creación del uso de los elementos.

Solución planteada

Implementamos un Abstract Factory que permite crear toda la familia de objetos para un determinado sistema operativo.

Por un lado las clases de creación correspondientes al Abstract Factory, los creadores concretos para Windows y para MacOS, y por otro las clases de Productos, correspondientes a botón (Button) y cuadro de texto (TextBox), cada una con sus propias implementaciones para Windows y MacOS.

El creador de Windows (WindowsFactory) creará el botón y el cuadro de texto correspondiente a Windows, y lo mismo hará el MacOSFactory, pero usando los elementos de MacOS.

abstract factory

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Definimos interfaces para los elementos básicos de la interfaz de usuario que queremos crear. En este caso, botones y cuadros de texto.


    interface Button {
        void paint();
    }                
                

    interface TextBox {
      void render();
    }
              

Creamos implementaciones específicas de las interfaces de botones y cuadros de texto para cada sistema operativo (Windows y MacOS).


    class WindowsButton implements Button {
      public void paint() { 
          System.out.println(“Render a button in a Windows style”); 
      }
    }
  

    class MacOSButton implements Button {
      public void paint() { 
          System.out.println(“Render a button in a MacOS style”); 
      }
    }
  

    class WindowsTextBox implements TextBox {
      public void render() { 
          System.out.println(“Render a text box in a Windows style”); 
      }
    }
  

    class MacOSTextBox implements TextBox {
      public void render() { 
          System.out.println(“Render a text box in a MacOS style”); 
      }
    }
  

Definimos una interfaz para la fábrica abstracta, que especifica los métodos para crear botones y cuadros de texto.


    interface GUIFactory {
      Button createButton();
      TextBox createTextBox();
    }
  

Implementamos fábricas concretas para cada sistema operativo. Estas fábricas saben cómo crear los elementos de la interfaz específica para su sistema operativo.


    class WindowsFactory implements GUIFactory {
      public Button createButton() {
          return new WindowsButton();
      }
      public TextBox createTextBox() {
          return new WindowsTextBox();
      }
    }                  
  

    class MacOSFactory implements GUIFactory {
      public Button createButton() {
          return new MacOSButton();
      }
      public TextBox createTextBox() {
          return new MacOSTextBox();
      }
    }
  

El cliente usa la fábrica abstracta para crear los elementos de la interfaz. No sabe qué implementación concreta está usando, algo que que permite cambiar las fábricas fácilmente sin alterar el resto del código.


    class Application {
      private Button button;
      private TextBox textBox;
  
      public Application(GUIFactory factory) {
          button = factory.createButton();
          textBox = factory.createTextBox();
      }
  
      public void paint() {
          button.paint();
          textBox.render();
      }
    }
  

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: FabricaAbstracta, FabricaConcreta, ProductoAbstracto, ProductoConcreto. Cliente:

  • GUIFactory(FabricaAbstracta): Interfaz para crear objetos Button y TextBox.
  • WindowsFactory, MacOSFactory (FabricaConcreta): Implementan la creación de la familia de productos para cada sistema operativo.
  • Button, TextBox (ProductoAbstracto): Interfaz de productos.
  • WindowsButton, MacOSButton, WindowsTextBox, MacOSTextBox (ProductoConcreto): Implementaciones específicas de cada producto para cada sistema operativo.
  • Application(Cliente): Usa la fabrica abstracta (GUIFactory) para crear y manipular objetos de tipo Button y TextBox.

Conclusiones

Este ejemplo demuestra el uso del Abstract Factory para crear familias de objetos relacionados (botones y cuadros de texto).

De esta manera podemos intercambiar fácilmente entre diferentes implementaciones (Windows y MacOS) sin cambiar el código del cliente.

Patrones relacionados

  • Factory Method

Las clases FabricaAbstracta suelen implementarse con métodos de fabricación (Factory Method). 

  • Prototype

Las clases FabricaAbstracta también pueden implementarse usando prototipos (Prototype), almacenando un conjunto de prototipos para clonarlos.

  • Singleton

Las Fabricas Concretas suele implementarse como Singletons.