Introducción

La idea detrás del patron Abstract Factory (que en español se traduciría como fabrica abstracta) consiste en la noción de que nuestro programa (o el cliente de una clase que nosotros proporcionamos) trabaja con una serie de productos (como los de una fábrica) que tienen unas determinadas características (por ejemplo tenemos productos embotellados y productos en tetrabrick). Nuestro programa va a utilizar dichos productos realizando una serie de acciones sobre ellos (como meter las botellas en unos camiones y los tetrabricks en otros) sin importarle quien le está suministrando los productos.

Así mismo existen una serie de fábricas que producen esos productos que vamos a tratar, una fábrica fabrica cocacola y otra pepsi pero ambas en botellas de las que nuestro programa trata. Al final, el concepto básico consiste en que a nuestro programa (o cliente) no le importa lo que haya dentro de la botella ni quien lo haya producido mientras sea una botella.

El punto de vista software

Desde el punto de vista software el ejemplo anterior se traduce en una situación en la que nuestro programa maneja un tipo de objetos con unas características comunes y algunas caracterísiticas propias. Esto en general en software se resuelve mediante el uso de dos características de los lenguajes de programación orientados a objetos, las clases abstractas y los interfaces.

Problema

El patrón de diseño Abstract Factory aborda el problema de la creación de familias de objetos (como por ejemplo iterfaces gráficos) que comparten toda una serie de características comunes en los objetos que componen dichas familias.

Aplicabilidad

El uso de este patrón está recomendado para situaciones en las que tenemos una familia de productos concretos y prevemos la inclusión de distintas familias de productos en un futuro.

Estructura

Este diagrama (sacado de la wikipedia inglesa) describe muy bien el patrón.

De esta forma nuestro programa realiza unas ciertas operaciones sobre dichas características comunes sin importarle que otras características tenga el objeto en cuestión. Por otro lado existen distintos productores de dichos objetos. Un ejemplo muy típico en muchos frameworks de programación que sigue este patrón se da en el caso de las familias de interfaces gráficos. Así existen diversas fábricas de interfaces que proporcionan sus propios botones, campos de texto, listas desplegables , etc… todas ellas basadas en los tipos básicos. Los clientes obtienen una familia y proceden a utilizar los controles usando el tipo abstracto genérico que da soporte.

Código de ejemplo

Veamos un ejemplo concreto (sin implementar).

// Tipo abstracto que describe una clase de
// control de comunicaciones
type TComControl = class
  // Inicializa la conexión (pero no conecta)
  procedure Init;
  // Manda un paquete keep alive
  procedure SendKeepAlive;
  // Establece la conexión
  procedure Connect(remoteName : string);
  // Termina la conexión
  procedure Disconnect;
end;
// Tipo abstracto que describe una clase de comunicaciones (enviar, recivir)
type TComHandler = class
  // Envia una cadena de texto
  procedure SendText(text : string);
  // Envia los datos de un stream
  procedure SentStream(buffer : TStream);
  // Recibe una cadena de texto
  //  Devuelve -1 si timeout, 0 en exito
  function ReadText(var text : string; timeout : integer);
  { Recibe un stream
    Devuelve -1 si timeout, 0 en exito
  function ReadStream(buffer : TStream; timeout : integer);
end;

type TComFactory = class
  procedure GetHandler : TComControl;
  procedure GetControl : TComHandler;
end;

Los dos tipos anteriores son los objetos abstractos que nos proveen de funcionalidad para realizar comunicaciones entre dos puntos. El objeto Control nos permite manejar nuestra conexión mientras que el objeto Handler nos permite mandar y recibir información.

unit ComUDP;

// Las instancias de los productos
// Control de conexión UDP
type TUDPControl = class(TComControl)
  procedure Init; override;
  procedure SendKeepAlive; override;
  procedure Connect(remoteName : string); override;
  procedure Disconnect; override;
end;

// Handler UDP
type TUPPHandler = class(TComHandler)
  procedure SendText(text : string);
  procedure SentStream(buffer : TStream);
  function ReadText(var text : string; timeout : integer);
  function ReadStream(buffer : TStream; timeout : integer);
end;

// Esta es la factory propiamente dicha
type TUDPFactory = class(TComFactory)
  procedure GetHandler : TComControl;
  procedure GetControl : TComHandler;
end;
unit ComTCP;

// Las instancias de los productos
// TCP Control
type TTCPControl= class
  procedure Init; override;
  procedure SendKeepAlive; override;
  procedure Connect(remoteName : string); override;
  procedure Disconnect; override;
end;

// TCP Handler
type TTCPHandler = class
  procedure SendText(text : string);
  procedure SentStream(buffer : TStream);
  function ReadText(var text : string; timeout : integer);
  function ReadStream(buffer : TStream; timeout : integer);
end;

// Esta es la factory propiamente dicha
type TTCPFactory = class(TComFactory)
  procedure GetHandler : TComControl;
  procedure GetControl : TComHandler;
end;

Por ultimo nuestro programa principal sería algo así

function GetFactory : TComFactory;
begin
  if Config.ComType = 'TCP' then
    result := TTCPFactory.Create
  else
    result := TUDPFactory.Create;
end;

program Comunicaciones;
var
  ComFactory : TComFactory;
  message : string;
begin
  ComFactory := GetFactory;
  ComFactory.GetControl.Init;
  ComFactory.GetControl.Connect;
  if (ComFactory.GetHandler.SendText('Hola mundo', 2000) = -1) then
     ShowMessage('Error mandando el mensaje');
  if (ComFactory.GetHandler.Read(message,2000) = -1) then
     ShowMessage('Error recibiendo el mensaje')
  else
     ShowMessage('Mensaje recibido: ' + message);
  ComFactory.GetControl.Disconnect;
end;

Ahora nuestro programa obtiene tan solo una fábrica y la utiliza sin preocuparse de que instancias concretas está utilizando sino centrandose tan solo en la funcionalidad básica de esas instancias.

Ejemplos reales

Familia de comunicaciones

Un caso bastante común es el similar al presentado en el ejemplo de código, es decir, una familia de algoritmos de comunicación por distintos medios que permiten el envio de información entre pares (por ejemplo). De esta forma nuestro programa puede usar comunicación TCP, UDP o cualquier otro protocolo que se nos ocurra sobre un dispositivo no estandar o que no soporte IP.

Interfaces gráficos

Otro caso relativamente común de uso de este patrón se da en la creación de familias de interfaces gráficos en las cuales los elementos (productos) del interfaz se mantienen constantes (por ejemplo labels, botones, cajas de texto …) pero el dibujado de dichos elementos puede delegarse en distintas familias (por ejemplo QT, GTK, etc) de forma que, en función de la fábrica seleccionada obtenemos unos botones u otros.