El patrón de diseño Fábrica Abstracta (Abstract Factory) proporciona una interfaz para crear familias de objetos relacionados o dependientes sin especificar sus clases concretas. Este patrón es una mejora del patrón Fábrica Simple y del patrón Fábrica por Métodos, permitiendo la inetgración de ambos.
Desde una perspectiva de diseño, la Fábrica Abstracta representa una abstracción de nivel superior. Se introduce una capa de abstracción para las fábricas, dividiéndolas en una fábrica abstracta (AbsFactory) y sus subclases de fábrica concretas. Los desarrolladores pueden elegir la sub-fábrica apropidaa según el tipo de objeto que necesiten crear. Esto transforma una única clase de fábrica simple en un "clúster de fábricas", lo que facilita el mantenimiento y la extensión del código.
Estructura y Ejemplos
Consideremos un ejemplo de creación de pizzas en diferentes ubicaciones (Beijing y Londres), cada una con sus variaciones de ingredientes.
Clase Base para Pizzas
Definimos una clase abstracta Pizza que servirá como base para todos los tipos de pizza.
// La clase Pizza se convierte en abstracta
public abstract class Pizza {
protected String nombre; // Nombre de la pizza
// Preparar ingredientes: varía según el tipo de pizza, por lo que es un método abstracto
public abstract void preparar();
public void hornear() {
System.out.println(nombre + " horneando;");
}
public void cortar() {
System.out.println(name + " cortando;");
}
// Empaquetar
public void empacar() {
System.out.println(name + " empacando;");
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
}
Pizzas Concretas
Creamos las implementaciones concretas para tipos de pizza específicos en cada ubicación.
public class BJQuesoPizza extends Pizza {
@Override
public void preparar() {
setNombre("Pizza de Queso de Beijing");
System.out.println(" Preparando ingredientes para Pizza de Queso de Beijing");
}
}
public class BJPepperPizza extends Pizza {
@Override
public void preparar() {
setNombre("Pizza de Pimienta de Beijing");
System.out.println(" Preparando ingredientes para Pizza de Pimienta de Beijing");
}
}
public class LDQuesoPizza extends Pizza {
@Override
public void preparar() {
setNombre("Pizza de Queso de Londres");
System.out.println(" Preparando ingredientes para Pizza de Queso de Londres");
}
}
public class LDPizzaPimienta extends Pizza {
@Override
public void preparar() {
setNombre("Pizza de Pimienta de Londres");
System.out.println(" Preparando ingredientes para Pizza de Pimienta de Londres");
}
}
Interfaz de Fábrica Abstracta
Definimos la interfaz que declara los métodos para crear objetos relacionados.
// Interfaz de la fábrica abstracta
public interface AbsFactory {
// Las subclases de fábrica concretas implementarán esto
public Pizza crearPizza(String tipoOrden);
}
Subclases de Fábrica Concretas
Implementamos las fábricas concretas para cada ubicación.
// Fábrica concreta para Beijing
public class BJFactory implements AbsFactory {
@Override
public Pizza crearPizza(String tipoOrden) {
System.out.println("~ Usando el patrón Fábrica Abstracta ~");
Pizza pizza = null;
if (tipoOrden.equals("queso")) {
pizza = new BJQuesoPizza();
} else if (tipoOrden.equals("pimienta")) {
pizza = new BJPepperPizza();
}
return pizza;
}
}
// Fábrica concreta para Londres
public class LDFactory implements AbsFactory {
@Override
public Pizza crearPizza(String tipoOrden) {
System.out.println("~ Usando el patrón Fábrica Abstracta ~");
Pizza pizza = null;
if (tipoOrden.equals("queso")) {
pizza = new LDQuesoPizza();
} else if (tipoOrden.equals("pimienta")) {
pizza = new LDPizzaPimienta();
}
return pizza;
}
}
Clase para Pedir Pizzas
Esta clase utiliza la fábrica abstratca para crear y procesar pizzas.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class OrdenPizza {
private AbsFactory fabrica;
// Constructor
public OrdenPizza(AbsFactory fabrica) {
setFabrica(fabrica);
}
private void setFabrica(AbsFactory fabrica) {
Pizza pizza = null;
String tipoOrden = ""; // Entrada del usuario
this.fabrica = fabrica;
do {
tipoOrden = obtenerTipo();
// fabrica puede ser una fábrica de Beijing o de Londres
pizza = fabrica.crearPizza(tipoOrden);
if (pizza != null) { // Pedido exitoso
pizza.preparar();
pizza.hornear();
pizza.cortar();
pizza.empacar();
} else {
System.out.println("Pedido fallido");
break;
}
} while (true);
}
// Método para obtener el tipo de pizza deseado por el cliente
private String obtenerTipo() {
try {
BufferedReader lector = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Ingrese el tipo de pizza:");
String str = lector.readLine();
return str;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
}
Punto de Entrada de la Aplicación
La clase principal demuestra cómo usar la Fábrica Abstracta para crear una tienda de pizzas.
public class TiendaPizza {
public static void main(String[] args) {
// Crear una tienda de pizzas en Beijing
new OrdenPizza(new BJFactory());
// Para crear una tienda en Londres, descomentar la siguiente línea:
// new OrdenPizza(new LDFactory());
}
}
Resultado Esperado (al pedir 'queso' en Beijing)
Ingrese el tipo de pizza:
queso
~ Usando el patrón Fábrica Abstracta ~
Preparando ingredientes para Pizza de Queso de Beijing
Pizza de Queso de Beijing horneando;
Pizza de Queso de Beijing cortando;
Pizza de Queso de Beijing empacando;
Resumen de Patrones de Fábrica
- Propósito de los Patrones de Fábrica: Extraer el código de instanciación de objetos y gestionarlo centralizadamente en una clase de fábrica. Esto desacopla el sistema principal de las dependencias de clases concretas, mejorando la extensibilidad y el mantenimiento del proyecto.
- Los Tres Patrones de Fábrica: Fábrica Simple, Fábrica por Métodos y Fábrica Abstracta.
- Principio de Dependencia de Abstracción en Diseño de Patrones:
- Al crear instancias de objetos, evite el uso directo de
new. En su lugar, encapsule esta operación en un método de fábrica que devuelva la instancia. Alternativamente, evite que las variables almacenen referencias directas a clases concretas. - Evite que las clases hereden de implementaciones concretas; prefiera heredar de clases abstractas o implementar interfaces.
- No sobrescriba métodos que ya tienen una implementación en la clase base.
- Al crear instancias de objetos, evite el uso directo de