Patrón Abstract Factory
Una interfaz para crear familias de objetos que tienen que ir juntos (los controles de un tema, las piezas de un driver de base de datos) sin decir sus clases concretas ni poder mezclar dos familias.
nivel intermedioTambién: fábrica abstracta, kit, factoría abstracta
Visualízalo paso a paso
Cambia los datos, dale a reproducir y sigue cada paso en el dibujo, en la línea de Java que se ejecuta y en sus variables.
Abstract Factory
Elige el tema y las piezas del formulario, en orden: todas salen de la misma fábrica.
Paso 1
La familia se elige una sola vez, al crear la fábrica: TemaOscuro. El otro tema queda apagado: el formulario no puede sacar piezas de él.
1FabricaUI fabrica = new TemaOscuro(); // fábrica = TemaOscuro, piezas = 0
2Formulario formulario = new Formulario(fabrica);
3
4class Formulario {
5 Formulario(FabricaUI fabrica) {
6 casilla = fabrica.crearCasilla();
7 boton = fabrica.crearBoton();
8 }
9 void mostrar() {
10 System.out.println(boton.pintar("Enviar"));
11 }
12}
13
14class TemaOscuro implements FabricaUI {
15 public Boton crearBoton() { return new BotonOscuro(); }
16 public Casilla crearCasilla() { return new CasillaOscura(); }
17}Variables
- fábrica
- TemaOscuro
- piezas
- 0
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
A veces no se crea un objeto suelto sino varios que tienen que encajar: el botón, la casilla y el menú de un mismo tema visual; la conexión, la sentencia y el resultado de una misma base de datos. Si cada uno se crea con su new por separado, es fácil acabar con un botón oscuro en un formulario claro.
El patrón Abstract Factory define una interfaz (la fábrica abstracta, FabricaUI) con un método fábrica por cada producto de la familia (crearBoton, crearCasilla). Cada familia es una clase que la implementa (TemaClaro, TemaOscuro) y crea sus productos concretos. El cliente recibe una fábrica y le pide las piezas: todas salen de la misma familia.
Cambiar de familia entera es cambiar un objeto, la fábrica, en un solo sitio del programa (normalmente al arrancar, según la configuración). Añadir una familia nueva es añadir clases. Lo que cuesta es añadir un producto nuevo a todas las familias: hay que tocar la interfaz y todas las fábricas.
Cuándo usarlo
- Hay varias familias de objetos relacionados y un programa tiene que usar los de una sola familia a la vez (temas, plataformas, bases de datos, niveles de dificultad).
- Se quiere poder cambiar de familia en un solo sitio, sin tocar el código que usa los objetos.
- Se quiere garantizar que nunca se mezclan piezas de familias distintas.
Cuándo no
- Si solo se crea un tipo de objeto: basta un Factory Method.
- Si los productos de la familia cambian a menudo: cada producto nuevo obliga a tocar todas las fábricas.
Participantes
- Productos abstractos. Una interfaz por cada pieza de la familia (
Boton,Casilla). - Productos concretos. Una clase por pieza y familia (
BotonClaro,BotonOscuro,CasillaClara…). - Fábrica abstracta. La interfaz con un método fábrica por pieza (
FabricaUIconcrearBotonycrearCasilla). - Fábricas concretas. Una por familia (
TemaClaro,TemaOscuro): cada método crea la pieza de su familia. - Cliente. Recibe una fábrica (
Formulario) y solo usa las interfaces: no sabe qué familia es.
Diagrama de clases
Ver el diagrama en PlantUML
1@startuml
2interface FabricaUI {
3 +crearBoton() : Boton
4 +crearCasilla() : Casilla
5}
6class TemaClaro
7class TemaOscuro
8interface Boton {
9 +pintar(texto : String) : String
10}
11interface Casilla {
12 +pintar(texto : String, marcada : boolean) : String
13}
14class BotonClaro
15class BotonOscuro
16class CasillaClara
17class CasillaOscura
18class Formulario {
19 -boton : Boton
20 -casilla : Casilla
21 +mostrar() : void
22}
23FabricaUI <|.. TemaClaro
24FabricaUI <|.. TemaOscuro
25Boton <|.. BotonClaro
26Boton <|.. BotonOscuro
27Casilla <|.. CasillaClara
28Casilla <|.. CasillaOscura
29TemaClaro ..> BotonClaro : crea
30TemaClaro ..> CasillaClara : crea
31TemaOscuro ..> BotonOscuro : crea
32TemaOscuro ..> CasillaOscura : crea
33Formulario ..> FabricaUI : usa
34@endumlPuedes copiarlo en el editor de diagramas UML y modificarlo.
El código
Controles de dos temas
Formulario funciona igual con los dos temas, y no puede mezclar un botón de uno con una casilla del otro.
1import java.util.*;
2
3// Los productos: cada uno tiene una versión por tema
4interface Boton { String pintar(String texto); }
5interface Casilla { String pintar(String texto, boolean marcada); }
6
7class BotonClaro implements Boton {
8 public String pintar(String texto) { return "[ " + texto + " ]"; }
9}
10class CasillaClara implements Casilla {
11 public String pintar(String texto, boolean marcada) { return (marcada ? "[x] " : "[ ] ") + texto; }
12}
13class BotonOscuro implements Boton {
14 public String pintar(String texto) { return "<< " + texto.toUpperCase() + " >>"; }
15}
16class CasillaOscura implements Casilla {
17 public String pintar(String texto, boolean marcada) { return (marcada ? "(*) " : "( ) ") + texto; }
18}
19
20/** La fábrica abstracta: crea una familia entera de productos que encajan entre sí. */
21interface FabricaUI {
22 Boton crearBoton();
23 Casilla crearCasilla();
24}
25
26class TemaClaro implements FabricaUI {
27 public Boton crearBoton() { return new BotonClaro(); }
28 public Casilla crearCasilla() { return new CasillaClara(); }
29}
30
31class TemaOscuro implements FabricaUI {
32 public Boton crearBoton() { return new BotonOscuro(); }
33 public Casilla crearCasilla() { return new CasillaOscura(); }
34}
35
36/** El cliente: monta el formulario sin saber de qué tema son las piezas, y nunca mezcla dos temas. */
37class Formulario {
38 private final Boton boton;
39 private final Casilla casilla;
40
41 Formulario(FabricaUI fabrica) {
42 boton = fabrica.crearBoton();
43 casilla = fabrica.crearCasilla();
44 }
45
46 void mostrar() {
47 System.out.println(casilla.pintar("Acepto las condiciones", true));
48 System.out.println(casilla.pintar("Quiero recibir novedades", false));
49 System.out.println(boton.pintar("Enviar"));
50 }
51}
52
53public class Main {
54 public static void main(String[] args) {
55 Map<String, FabricaUI> temas = new LinkedHashMap<>();
56 temas.put("claro", new TemaClaro());
57 temas.put("oscuro", new TemaOscuro());
58 for (var tema : temas.entrySet()) {
59 System.out.println("Tema " + tema.getKey() + ":");
60 new Formulario(tema.getValue()).mostrar();
61 }
62 }
63}from abc import ABC, abstractmethod
# Los productos: cada uno tiene una versión por tema
class BotonClaro:
def pintar(self, texto):
return f"[ {texto} ]"
class CasillaClara:
def pintar(self, texto, marcada):
return ("[x] " if marcada else "[ ] ") + texto
class BotonOscuro:
def pintar(self, texto):
return f"<< {texto.upper()} >>"
class CasillaOscura:
def pintar(self, texto, marcada):
return ("(*) " if marcada else "( ) ") + texto
class FabricaUI(ABC):
"""La fábrica abstracta: crea una familia entera de productos que encajan entre sí."""
@abstractmethod
def crear_boton(self): ...
@abstractmethod
def crear_casilla(self): ...
class TemaClaro(FabricaUI):
def crear_boton(self):
return BotonClaro()
def crear_casilla(self):
return CasillaClara()
class TemaOscuro(FabricaUI):
def crear_boton(self):
return BotonOscuro()
def crear_casilla(self):
return CasillaOscura()
class Formulario:
"""El cliente: monta el formulario sin saber de qué tema son las piezas, y nunca mezcla dos temas."""
def __init__(self, fabrica):
self.boton = fabrica.crear_boton()
self.casilla = fabrica.crear_casilla()
def mostrar(self):
print(self.casilla.pintar("Acepto las condiciones", True))
print(self.casilla.pintar("Quiero recibir novedades", False))
print(self.boton.pintar("Enviar"))
temas = {"claro": TemaClaro(), "oscuro": TemaOscuro()}
for nombre, fabrica in temas.items():
print(f"Tema {nombre}:")
Formulario(fabrica).mostrar()// Los productos: cada uno tiene una versión por tema
class BotonClaro { pintar(texto) { return `[ ${texto} ]`; } }
class CasillaClara { pintar(texto, marcada) { return (marcada ? "[x] " : "[ ] ") + texto; } }
class BotonOscuro { pintar(texto) { return `<< ${texto.toUpperCase()} >>`; } }
class CasillaOscura { pintar(texto, marcada) { return (marcada ? "(*) " : "( ) ") + texto; } }
// Las fábricas concretas: cada una crea una familia entera de productos que encajan entre sí
class TemaClaro {
crearBoton() { return new BotonClaro(); }
crearCasilla() { return new CasillaClara(); }
}
class TemaOscuro {
crearBoton() { return new BotonOscuro(); }
crearCasilla() { return new CasillaOscura(); }
}
/** El cliente: monta el formulario sin saber de qué tema son las piezas, y nunca mezcla dos temas. */
class Formulario {
constructor(fabrica) {
this.boton = fabrica.crearBoton();
this.casilla = fabrica.crearCasilla();
}
mostrar() {
console.log(this.casilla.pintar("Acepto las condiciones", true));
console.log(this.casilla.pintar("Quiero recibir novedades", false));
console.log(this.boton.pintar("Enviar"));
}
}
const temas = new Map([["claro", new TemaClaro()], ["oscuro", new TemaOscuro()]]);
for (const [nombre, fabrica] of temas) {
console.log(`Tema ${nombre}:`);
new Formulario(fabrica).mostrar();
}using System;
using System.Collections.Generic;
// Los productos: cada uno tiene una versión por tema
interface IBoton { string Pintar(string texto); }
interface ICasilla { string Pintar(string texto, bool marcada); }
class BotonClaro : IBoton { public string Pintar(string texto) => quot;[ {texto} ]"; }
class CasillaClara : ICasilla { public string Pintar(string texto, bool marcada) => (marcada ? "[x] " : "[ ] ") + texto; }
class BotonOscuro : IBoton { public string Pintar(string texto) => quot;<< {texto.ToUpperInvariant()} >>"; }
class CasillaOscura : ICasilla { public string Pintar(string texto, bool marcada) => (marcada ? "(*) " : "( ) ") + texto; }
// La fábrica abstracta: crea una familia entera de productos que encajan entre sí.
interface IFabricaUI {
IBoton CrearBoton();
ICasilla CrearCasilla();
}
class TemaClaro : IFabricaUI {
public IBoton CrearBoton() => new BotonClaro();
public ICasilla CrearCasilla() => new CasillaClara();
}
class TemaOscuro : IFabricaUI {
public IBoton CrearBoton() => new BotonOscuro();
public ICasilla CrearCasilla() => new CasillaOscura();
}
// El cliente: monta el formulario sin saber de qué tema son las piezas, y nunca mezcla dos temas.
class Formulario {
private readonly IBoton boton;
private readonly ICasilla casilla;
public Formulario(IFabricaUI fabrica) {
boton = fabrica.CrearBoton();
casilla = fabrica.CrearCasilla();
}
public void Mostrar() {
Console.WriteLine(casilla.Pintar("Acepto las condiciones", true));
Console.WriteLine(casilla.Pintar("Quiero recibir novedades", false));
Console.WriteLine(boton.Pintar("Enviar"));
}
}
class Program {
static void Main() {
var temas = new List<(string Nombre, IFabricaUI Fabrica)> { ("claro", new TemaClaro()), ("oscuro", new TemaOscuro()) };
foreach (var (nombre, fabrica) in temas) {
Console.WriteLine(quot;Tema {nombre}:");
new Formulario(fabrica).Mostrar();
}
}
}<?php
// Los productos: cada uno tiene una versión por tema
interface Boton { public function pintar(string $texto): string; }
interface Casilla { public function pintar(string $texto, bool $marcada): string; }
class BotonClaro implements Boton {
public function pintar(string $texto): string { return "[ $texto ]"; }
}
class CasillaClara implements Casilla {
public function pintar(string $texto, bool $marcada): string { return ($marcada ? "[x] " : "[ ] ") . $texto; }
}
class BotonOscuro implements Boton {
public function pintar(string $texto): string { return "<< " . mb_strtoupper($texto) . " >>"; }
}
class CasillaOscura implements Casilla {
public function pintar(string $texto, bool $marcada): string { return ($marcada ? "(*) " : "( ) ") . $texto; }
}
/** La fábrica abstracta: crea una familia entera de productos que encajan entre sí. */
interface FabricaUI {
public function crearBoton(): Boton;
public function crearCasilla(): Casilla;
}
class TemaClaro implements FabricaUI {
public function crearBoton(): Boton { return new BotonClaro(); }
public function crearCasilla(): Casilla { return new CasillaClara(); }
}
class TemaOscuro implements FabricaUI {
public function crearBoton(): Boton { return new BotonOscuro(); }
public function crearCasilla(): Casilla { return new CasillaOscura(); }
}
/** El cliente: monta el formulario sin saber de qué tema son las piezas, y nunca mezcla dos temas. */
class Formulario {
private Boton $boton;
private Casilla $casilla;
public function __construct(FabricaUI $fabrica) {
$this->boton = $fabrica->crearBoton();
$this->casilla = $fabrica->crearCasilla();
}
public function mostrar(): void {
echo $this->casilla->pintar("Acepto las condiciones", true), "\n";
echo $this->casilla->pintar("Quiero recibir novedades", false), "\n";
echo $this->boton->pintar("Enviar"), "\n";
}
}
$temas = ["claro" => new TemaClaro(), "oscuro" => new TemaOscuro()];
foreach ($temas as $nombre => $fabrica) {
echo "Tema $nombre:\n";
(new Formulario($fabrica))->mostrar();
}Salida al ejecutarlo (la misma en los 5 lenguajes)
Tema claro: [x] Acepto las condiciones [ ] Quiero recibir novedades [ Enviar ] Tema oscuro: (*) Acepto las condiciones ( ) Quiero recibir novedades << ENVIAR >>
Elegir la familia al arrancar (y JDBC)
La fábrica se decide en un solo sitio. JDBC es una fábrica abstracta que ya usas: el driver crea todas las piezas de su base de datos.
1// La familia se elige una vez, al arrancar; el resto del programa solo ve FabricaUI.
2FabricaUI fabrica = switch (config.getProperty("tema", "claro")) {
3 case "oscuro" -> new TemaOscuro();
4 default -> new TemaClaro();
5};
6Formulario formulario = new Formulario(fabrica);
7
8// JDBC funciona igual: el driver es una fábrica de una familia (conexión, sentencia, resultado).
9// Con la URL de MySQL todas las piezas son de MySQL; con la de PostgreSQL, todas de PostgreSQL,
10// y este código no cambia.
11try (Connection con = DriverManager.getConnection(url, usuario, clave);
12 PreparedStatement ps = con.prepareStatement("SELECT nombre FROM alumno WHERE ciclo = ?")) {
13 ps.setString(1, "DAM");
14 try (ResultSet rs = ps.executeQuery()) {
15 while (rs.next()) System.out.println(rs.getString("nombre"));
16 }
17}# La familia se elige una vez, al arrancar; el resto del programa solo ve la fábrica.
fabrica = TemaOscuro() if config.get("tema") == "oscuro" else TemaClaro()
formulario = Formulario(fabrica)
# La DB-API funciona igual: la conexión de sqlite3 crea cursores de sqlite3; con psycopg
# (PostgreSQL) todas las piezas serían de psycopg y este código no cambiaría.
with sqlite3.connect("instituto.db") as con:
cur = con.cursor()
cur.execute("SELECT nombre FROM alumno WHERE ciclo = ?", ("DAM",))
for (nombre,) in cur.fetchall():
print(nombre)// La familia se elige una vez, al arrancar; el resto del programa solo ve la fábrica.
const oscuro = matchMedia("(prefers-color-scheme: dark)").matches;
const fabrica = oscuro ? new TemaOscuro() : new TemaClaro();
const formulario = new Formulario(fabrica);
// document también crea familias: los elementos de un mismo espacio de nombres (SVG aquí)
// encajan entre sí; con createElement saldrían elementos HTML.
const SVG = "http://www.w3.org/2000/svg";
const dibujo = document.createElementNS(SVG, "svg");
const circulo = document.createElementNS(SVG, "circle");
dibujo.append(circulo);// La familia se elige una vez, al arrancar; el resto del programa solo ve la fábrica. IFabricaUI fabrica = config["tema"] == "oscuro" ? new TemaOscuro() : new TemaClaro(); var formulario = new Formulario(fabrica); // ADO.NET tiene la fábrica abstracta con nombre propio: DbProviderFactory. Con la de SQLite // todas las piezas son de SQLite; con la de SQL Server, todas de SQL Server. DbProviderFactory db = SqliteFactory.Instance; using DbConnection con = db.CreateConnection()!; con.ConnectionString = "Data Source=instituto.db"; con.Open(); using DbCommand cmd = con.CreateCommand(); cmd.CommandText = "SELECT nombre FROM alumno"; using DbDataReader lector = cmd.ExecuteReader();
<?php
// La familia se elige una vez, al arrancar; el resto del programa solo ve la fábrica.
$fabrica = ($config["tema"] ?? "claro") === "oscuro" ? new TemaOscuro() : new TemaClaro();
$formulario = new Formulario($fabrica);
// PDO funciona igual: según el DSN, la conexión y las sentencias que crea son de MySQL,
// de PostgreSQL o de SQLite, y este código no cambia.
$pdo = new PDO("mysql:host=localhost;dbname=instituto", $usuario, $clave);
$st = $pdo->prepare("SELECT nombre FROM alumno WHERE ciclo = ?");
$st->execute(["DAM"]);
foreach ($st as $fila) echo $fila["nombre"], "\n";En la práctica
- JDBC:
DriverManagerda unaConnectiondel driver de la URL, y esa conexión creaPreparedStatementyResultSetdel mismo driver. En .NET,DbProviderFactoryhace exactamente esto. DocumentBuilderFactoryyTransformerFactoryde XML: según la implementación instalada, crean analizadores y transformadores que trabajan juntos.- Los temas de una interfaz (claro, oscuro, alto contraste) y las bibliotecas multiplataforma que crean controles de Windows, macOS o Linux.
- Los juegos con niveles de dificultad: la fábrica del modo difícil crea enemigos, armas y obstáculos de ese modo.
Errores típicos
- Crear las piezas de la familia por separado (
new BotonOscuro()junto anew CasillaClara()): se pierde la garantía de que encajan. - Elegir la familia con un
ifen cada sitio que crea una pieza: la elección debe hacerse una vez, al crear la fábrica. - Confundirlo con Factory Method: aquí la fábrica es un objeto con un método por cada producto de una familia; allí, un método que crea un solo tipo de producto.
- Meter en la fábrica abstracta productos que solo usa una familia: obliga a las demás a implementar métodos que no tienen sentido para ellas.
Ejercicios
Cada ejercicio se corrige solo con sus pruebas (algunas ocultas). Escribe tu solución en el editor y pulsa Ejecutar o Comprobar; la solución explicada está debajo, por si te atascas.
1. Muebles de una misma colección
La tienda vende muebles por estilos, y un pedido nunca mezcla estilos: el main pide cada pieza a la fábrica del estilo elegido. Ya está el estilo moderno. Escribe las familias Clasico y Nordico, con sus tres muebles, y regístralas en el main con las claves clasico y nordico.
- Órdenes:
estilo nombre(escribeEstilo: nombre, oEstilo desconocido: nombrey se queda el anterior) ypedido pieza pieza…consilla,mesaosofa: una línea por piezaDescripción: precio €yTotal: X €. - Clásico: «Silla clásica de madera tallada» 60 €, «Mesa clásica de roble» 210 €, «Sofá clásico de terciopelo» 520 €; su
estilo()esclásico. - Nórdico: «Silla nórdica de pino» 39 €, «Mesa nórdica de abedul» 99 €, «Sofá nórdico de lino» 299 €; su
estilo()esnórdico.
Ejemplo
estilo nordico pedido silla silla mesa estilo clasico pedido sofa mesa estilo moderno pedido sofa
Estilo: nórdico Silla nórdica de pino: 39,00 € Silla nórdica de pino: 39,00 € Mesa nórdica de abedul: 99,00 € Total: 177,00 € Estilo: clásico Sofá clásico de terciopelo: 520,00 € Mesa clásica de roble: 210,00 € Total: 730,00 € Estilo: moderno Sofá moderno de piel: 380,00 € Total: 380,00 €
Ver la solución explicada
1import java.util.*;
2
3interface Silla { String describir(); double precio(); }
4interface Mesa { String describir(); double precio(); }
5interface Sofa { String describir(); double precio(); }
6
7/** La fábrica abstracta: los muebles de un mismo estilo. */
8interface FabricaMuebles {
9 String estilo();
10 Silla crearSilla();
11 Mesa crearMesa();
12 Sofa crearSofa();
13}
14
15class SillaModerna implements Silla {
16 public String describir() { return "Silla moderna de metal"; }
17 public double precio() { return 45; }
18}
19class MesaModerna implements Mesa {
20 public String describir() { return "Mesa moderna de cristal"; }
21 public double precio() { return 120; }
22}
23class SofaModerno implements Sofa {
24 public String describir() { return "Sofá moderno de piel"; }
25 public double precio() { return 380; }
26}
27
28class Moderno implements FabricaMuebles {
29 public String estilo() { return "moderno"; }
30 public Silla crearSilla() { return new SillaModerna(); }
31 public Mesa crearMesa() { return new MesaModerna(); }
32 public Sofa crearSofa() { return new SofaModerno(); }
33}
34
35class SillaClasica implements Silla {
36 public String describir() { return "Silla clásica de madera tallada"; }
37 public double precio() { return 60; }
38}
39class MesaClasica implements Mesa {
40 public String describir() { return "Mesa clásica de roble"; }
41 public double precio() { return 210; }
42}
43class SofaClasico implements Sofa {
44 public String describir() { return "Sofá clásico de terciopelo"; }
45 public double precio() { return 520; }
46}
47
48class Clasico implements FabricaMuebles {
49 public String estilo() { return "clásico"; }
50 public Silla crearSilla() { return new SillaClasica(); }
51 public Mesa crearMesa() { return new MesaClasica(); }
52 public Sofa crearSofa() { return new SofaClasico(); }
53}
54
55class SillaNordica implements Silla {
56 public String describir() { return "Silla nórdica de pino"; }
57 public double precio() { return 39; }
58}
59class MesaNordica implements Mesa {
60 public String describir() { return "Mesa nórdica de abedul"; }
61 public double precio() { return 99; }
62}
63class SofaNordico implements Sofa {
64 public String describir() { return "Sofá nórdico de lino"; }
65 public double precio() { return 299; }
66}
67
68class Nordico implements FabricaMuebles {
69 public String estilo() { return "nórdico"; }
70 public Silla crearSilla() { return new SillaNordica(); }
71 public Mesa crearMesa() { return new MesaNordica(); }
72 public Sofa crearSofa() { return new SofaNordico(); }
73}
74
75public class Main {
76 static String euros(double x) {
77 return String.format(Locale.ROOT, "%.2f", x).replace('.', ',') + " €";
78 }
79
80 public static void main(String[] args) {
81 Map<String, FabricaMuebles> estilos = new HashMap<>();
82 estilos.put("moderno", new Moderno());
83 estilos.put("clasico", new Clasico());
84 estilos.put("nordico", new Nordico());
85 Scanner sc = new Scanner(System.in);
86 FabricaMuebles fabrica = null;
87 while (sc.hasNextLine()) {
88 String[] p = sc.nextLine().trim().split("\\s+");
89 if (p[0].isEmpty()) continue;
90 if (p[0].equals("estilo") && p.length == 2) {
91 FabricaMuebles f = estilos.get(p[1]);
92 if (f == null) System.out.println("Estilo desconocido: " + p[1]);
93 else {
94 fabrica = f;
95 System.out.println("Estilo: " + fabrica.estilo());
96 }
97 } else if (p[0].equals("pedido")) {
98 if (fabrica == null) {
99 System.out.println("Elige primero un estilo");
100 continue;
101 }
102 double total = 0;
103 for (int i = 1; i < p.length; i++) {
104 String descripcion;
105 double precio;
106 switch (p[i]) {
107 case "silla" -> { Silla s = fabrica.crearSilla(); descripcion = s.describir(); precio = s.precio(); }
108 case "mesa" -> { Mesa m = fabrica.crearMesa(); descripcion = m.describir(); precio = m.precio(); }
109 case "sofa" -> { Sofa s = fabrica.crearSofa(); descripcion = s.describir(); precio = s.precio(); }
110 default -> { System.out.println(" Pieza desconocida: " + p[i]); continue; }
111 }
112 System.out.println(" " + descripcion + ": " + euros(precio));
113 total += precio;
114 }
115 System.out.println("Total: " + euros(total));
116 } else {
117 System.out.println("Orden no válida: " + String.join(" ", p));
118 }
119 }
120 }
121}Cambiar de colección es cambiar la fábrica (fabrica = f); el código que crea las piezas no se entera.
Como todas las piezas de un pedido salen de la misma fábrica, es imposible vender una silla nórdica con una mesa clásica por error.
2. Un documento en tres formatos
El programa escribe un documento pieza a pieza (títulos, párrafos y listas) con la familia del formato elegido. Ya está el formato de texto. Escribe las familias Html y Markdown y regístralas en el main con las claves html y markdown.
- Órdenes:
formato nombre(escribe--- nombre ---, oFormato desconocido: nombre),titulo texto,parrafo textoylista a | b | c. Se empieza en formato texto. - HTML:
<h1>texto</h1>,<p>texto</p>y la lista como<ul>, una línea<li>elemento</li>por elemento y</ul>. En el texto,&se escribe&,<es<y>es>(en este orden: primero el&). - Markdown:
# texto, el párrafo tal cual y una línea- elementopor elemento.
Ejemplo
titulo Notas de DAM parrafo Primera evaluación. lista Ana: 8 | Luis: 6 formato html titulo Notas de DAM parrafo Primera evaluación. lista Ana: 8 | Luis: 6 formato markdown titulo Notas de DAM lista Ana: 8 | Luis: 6
NOTAS DE DAM ============ Primera evaluación. * Ana: 8 * Luis: 6 --- html --- <h1>Notas de DAM</h1> <p>Primera evaluación.</p> <ul> <li>Ana: 8</li> <li>Luis: 6</li> </ul> --- markdown --- # Notas de DAM - Ana: 8 - Luis: 6
Ver la solución explicada
1import java.util.*;
2
3interface Titulo { String pintar(String texto); }
4interface Parrafo { String pintar(String texto); }
5interface Lista { String pintar(List<String> elementos); }
6
7/** Una familia por formato de salida: un documento nunca mezcla piezas de dos formatos. */
8interface FabricaDocumento {
9 Titulo crearTitulo();
10 Parrafo crearParrafo();
11 Lista crearLista();
12}
13
14class TituloTexto implements Titulo {
15 public String pintar(String t) { return t.toUpperCase() + "\n" + "=".repeat(t.length()); }
16}
17class ParrafoTexto implements Parrafo {
18 public String pintar(String t) { return t; }
19}
20class ListaTexto implements Lista {
21 public String pintar(List<String> elementos) {
22 StringBuilder sb = new StringBuilder();
23 for (int i = 0; i < elementos.size(); i++) sb.append(i > 0 ? "\n" : "").append(" * ").append(elementos.get(i));
24 return sb.toString();
25 }
26}
27
28class Texto implements FabricaDocumento {
29 public Titulo crearTitulo() { return new TituloTexto(); }
30 public Parrafo crearParrafo() { return new ParrafoTexto(); }
31 public Lista crearLista() { return new ListaTexto(); }
32}
33
34class Html implements FabricaDocumento {
35 static String escapar(String t) {
36 return t.replace("&", "&").replace("<", "<").replace(">", ">");
37 }
38 public Titulo crearTitulo() { return t -> "<h1>" + escapar(t) + "</h1>"; }
39 public Parrafo crearParrafo() { return t -> "<p>" + escapar(t) + "</p>"; }
40 public Lista crearLista() {
41 return elementos -> {
42 StringBuilder sb = new StringBuilder("<ul>");
43 for (String e : elementos) sb.append("\n <li>").append(escapar(e)).append("</li>");
44 return sb.append("\n</ul>").toString();
45 };
46 }
47}
48
49class Markdown implements FabricaDocumento {
50 public Titulo crearTitulo() { return t -> "# " + t; }
51 public Parrafo crearParrafo() { return t -> t; }
52 public Lista crearLista() {
53 return elementos -> {
54 List<String> lineas = new ArrayList<>();
55 for (String e : elementos) lineas.add("- " + e);
56 return String.join("\n", lineas);
57 };
58 }
59}
60
61public class Main {
62 public static void main(String[] args) {
63 Map<String, FabricaDocumento> formatos = new HashMap<>();
64 formatos.put("texto", new Texto());
65 formatos.put("html", new Html());
66 formatos.put("markdown", new Markdown());
67 Scanner sc = new Scanner(System.in);
68 FabricaDocumento fabrica = formatos.get("texto");
69 while (sc.hasNextLine()) {
70 String linea = sc.nextLine().trim();
71 if (linea.isEmpty()) continue;
72 int esp = linea.indexOf(' ');
73 String orden = esp < 0 ? linea : linea.substring(0, esp);
74 String resto = esp < 0 ? "" : linea.substring(esp + 1).trim();
75 switch (orden) {
76 case "formato" -> {
77 FabricaDocumento f = formatos.get(resto);
78 if (f == null) System.out.println("Formato desconocido: " + resto);
79 else {
80 fabrica = f;
81 System.out.println("--- " + resto + " ---");
82 }
83 }
84 case "titulo" -> System.out.println(fabrica.crearTitulo().pintar(resto));
85 case "parrafo" -> System.out.println(fabrica.crearParrafo().pintar(resto));
86 case "lista" -> {
87 List<String> elementos = new ArrayList<>();
88 for (String e : resto.split("\\|")) if (!e.isBlank()) elementos.add(e.trim());
89 if (elementos.isEmpty()) System.out.println("Lista vacía");
90 else System.out.println(fabrica.crearLista().pintar(elementos));
91 }
92 default -> System.out.println("Orden no válida: " + linea);
93 }
94 }
95 }
96}El main pide un título, un párrafo o una lista a la fábrica actual, y las tres piezas salen siempre del mismo formato.
Cuando los productos tienen un solo método, las fábricas concretas pueden devolver lambdas: el patrón es el mismo con mucho menos código.
Test
Test: Abstract Factory
0/5 respondidas · 0 aciertosElige una respuesta en cada pregunta: verás al momento si es correcta y por qué. Con un 80 % de aciertos se da por superada.
1.¿Qué crea una fábrica abstracta?
2.¿Qué cuesta más en una Abstract Factory?
3.¿Dónde se decide qué familia se usa?
4.¿Qué API de Java funciona como una fábrica abstracta?
5.¿En qué se diferencia de Factory Method?