Patrón Factory Method
Un método que crea los objetos en lugar de hacer new en el código que los usa: cada subclase (o cada caso) decide qué clase concreta crear, y el resto del programa solo conoce la interfaz.
nivel básicoTambién: método fábrica, factoría, fábrica, factory
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.
Factory Method
Elige por qué canales se avisa, en orden, y el texto del aviso. Cada canal decide qué aviso crea.
Paso 1
Canal tiene la lógica común (notificar) y declara el método fábrica crearAviso() sin decir qué aviso crea: eso lo decide cada subclase.
1abstract class Canal {
2 protected abstract Aviso crearAviso(); // canal = —, avisos creados = 0
3
4 void notificar(String texto) {
5 Aviso aviso = crearAviso();
6 System.out.println(aviso.enviar(texto));
7 }
8}
9
10class CanalSms extends Canal {
11 protected Aviso crearAviso() {
12 return new Sms("600 111 222");
13 }
14}Variables
- canal
- —
- avisos creados
- 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
Cada new Email(...) escrito en medio de la lógica ata ese código a la clase Email. Si mañana hay que enviar también SMS o notificaciones al móvil, toca buscar todos los new y llenarlos de if.
El patrón Factory Method saca la creación a un método, el método fábrica, que devuelve el producto por su interfaz (Aviso). En la versión de los libros, una clase abstracta (el creador, Canal) tiene la lógica común y declara el método fábrica abstracto; cada subclase lo implementa creando su producto concreto. La lógica común usa el producto sin saber qué clase es.
En el día a día se usa todavía más la «fábrica simple»: un método estático que recibe un nombre o una configuración y devuelve el objeto adecuado (List.of, Calendar.getInstance, Integer.valueOf). No es exactamente el patrón de los libros, pero resuelve el mismo problema: que solo un sitio del programa sepa qué clase se crea.
Cuándo usarlo
- El código que usa un objeto no debería saber de qué clase concreta es (avisos, conexiones, exportadores, figuras).
- Una clase base tiene un algoritmo común y solo cambia el objeto que se crea en un paso.
- Se quiere poder añadir un tipo nuevo creando una clase, sin tocar el código que ya funciona.
- Crear el objeto tiene lógica (validar, elegir según la configuración, reutilizar objetos ya creados) que no debería repetirse.
Cuándo no
- Si solo hay una clase posible y no va a haber más: un
newes más claro. - Si la jerarquía de creadores solo existe para elegir el producto: una fábrica simple (un método estático o un
Mapde nombre a constructor) es más ligera.
Participantes
- Producto. La interfaz de lo que se crea (
Aviso). - Productos concretos. Las clases que la implementan (
Email,Sms,Push). - Creador. La clase abstracta (
Canal) con la lógica común (notificar) y el método fábrica abstracto (crearAviso), que devuelve unAviso. - Creadores concretos. Cada subclase (
CanalSms…) implementa el método fábrica creando su producto concreto.
Diagrama de clases
Ver el diagrama en PlantUML
1@startuml
2interface Aviso {
3 +enviar(texto : String) : String
4}
5class Email
6class Sms
7class Push
8abstract class Canal {
9 +notificar(texto : String) : void
10 #{abstract} crearAviso() : Aviso
11}
12class CanalEmail {
13 #crearAviso() : Aviso
14}
15class CanalSms {
16 #crearAviso() : Aviso
17}
18class CanalPush {
19 #crearAviso() : Aviso
20}
21Aviso <|.. Email
22Aviso <|.. Sms
23Aviso <|.. Push
24Canal <|-- CanalEmail
25Canal <|-- CanalSms
26Canal <|-- CanalPush
27Canal ..> Aviso : usa
28CanalEmail ..> Email : crea
29CanalSms ..> Sms : crea
30CanalPush ..> Push : crea
31@endumlPuedes copiarlo en el editor de diagramas UML y modificarlo.
El código
Un canal por tipo de aviso
notificar está escrito una sola vez en Canal y funciona con cualquier aviso: cada subclase solo dice cuál crea.
1import java.util.*;
2
3/** El producto: lo que crea el método fábrica. */
4interface Aviso {
5 String enviar(String texto);
6}
7
8class Email implements Aviso {
9 private final String direccion;
10 Email(String direccion) { this.direccion = direccion; }
11 public String enviar(String texto) { return "Email a " + direccion + ": " + texto; }
12}
13
14class Sms implements Aviso {
15 private final String telefono;
16 Sms(String telefono) { this.telefono = telefono; }
17 public String enviar(String texto) {
18 String corto = texto.length() > 20 ? texto.substring(0, 17) + "..." : texto; // un SMS es corto
19 return "SMS a " + telefono + ": " + corto;
20 }
21}
22
23class Push implements Aviso {
24 public String enviar(String texto) { return "Notificación en el móvil: " + texto; }
25}
26
27/** El creador: tiene la lógica común y deja que cada subclase decida qué aviso crear. */
28abstract class Canal {
29 protected abstract Aviso crearAviso(); // el método fábrica
30
31 void notificar(String texto) {
32 Aviso aviso = crearAviso(); // no sabe (ni le importa) qué clase concreta es
33 System.out.println(aviso.enviar(texto));
34 }
35}
36
37class CanalEmail extends Canal {
38 protected Aviso crearAviso() { return new Email("ana@correo.es"); }
39}
40
41class CanalSms extends Canal {
42 protected Aviso crearAviso() { return new Sms("600 111 222"); }
43}
44
45class CanalPush extends Canal {
46 protected Aviso crearAviso() { return new Push(); }
47}
48
49public class Main {
50 public static void main(String[] args) {
51 List<Canal> canales = List.of(new CanalEmail(), new CanalSms(), new CanalPush());
52 for (Canal canal : canales) canal.notificar("Tu pedido ha salido del almacén");
53 }
54}from abc import ABC, abstractmethod
class Aviso(ABC):
"""El producto: lo que crea el método fábrica."""
@abstractmethod
def enviar(self, texto): ...
class Email(Aviso):
def __init__(self, direccion):
self.direccion = direccion
def enviar(self, texto):
return f"Email a {self.direccion}: {texto}"
class Sms(Aviso):
def __init__(self, telefono):
self.telefono = telefono
def enviar(self, texto):
corto = texto[:17] + "..." if len(texto) > 20 else texto # un SMS es corto
return f"SMS a {self.telefono}: {corto}"
class Push(Aviso):
def enviar(self, texto):
return f"Notificación en el móvil: {texto}"
class Canal(ABC):
"""El creador: tiene la lógica común y deja que cada subclase decida qué aviso crear."""
@abstractmethod
def crear_aviso(self) -> Aviso: ... # el método fábrica
def notificar(self, texto):
aviso = self.crear_aviso() # no sabe (ni le importa) qué clase concreta es
print(aviso.enviar(texto))
class CanalEmail(Canal):
def crear_aviso(self):
return Email("ana@correo.es")
class CanalSms(Canal):
def crear_aviso(self):
return Sms("600 111 222")
class CanalPush(Canal):
def crear_aviso(self):
return Push()
for canal in [CanalEmail(), CanalSms(), CanalPush()]:
canal.notificar("Tu pedido ha salido del almacén")// Los productos: lo que crea el método fábrica (en JavaScript basta con que tengan enviar)
class Email {
constructor(direccion) { this.direccion = direccion; }
enviar(texto) { return `Email a ${this.direccion}: ${texto}`; }
}
class Sms {
constructor(telefono) { this.telefono = telefono; }
enviar(texto) {
const corto = texto.length > 20 ? texto.slice(0, 17) + "..." : texto; // un SMS es corto
return `SMS a ${this.telefono}: ${corto}`;
}
}
class Push {
enviar(texto) { return `Notificación en el móvil: ${texto}`; }
}
/** El creador: tiene la lógica común y deja que cada subclase decida qué aviso crear. */
class Canal {
crearAviso() { throw new Error("Cada canal crea su aviso"); } // el método fábrica
notificar(texto) {
const aviso = this.crearAviso(); // no sabe (ni le importa) qué clase concreta es
console.log(aviso.enviar(texto));
}
}
class CanalEmail extends Canal { crearAviso() { return new Email("ana@correo.es"); } }
class CanalSms extends Canal { crearAviso() { return new Sms("600 111 222"); } }
class CanalPush extends Canal { crearAviso() { return new Push(); } }
for (const canal of [new CanalEmail(), new CanalSms(), new CanalPush()]) {
canal.notificar("Tu pedido ha salido del almacén");
}using System;
using System.Collections.Generic;
// El producto: lo que crea el método fábrica.
interface IAviso {
string Enviar(string texto);
}
class Email : IAviso {
private readonly string direccion;
public Email(string direccion) => this.direccion = direccion;
public string Enviar(string texto) => quot;Email a {direccion}: {texto}";
}
class Sms : IAviso {
private readonly string telefono;
public Sms(string telefono) => this.telefono = telefono;
public string Enviar(string texto) {
string corto = texto.Length > 20 ? texto[..17] + "..." : texto; // un SMS es corto
return quot;SMS a {telefono}: {corto}";
}
}
class Push : IAviso {
public string Enviar(string texto) => quot;Notificación en el móvil: {texto}";
}
// El creador: tiene la lógica común y deja que cada subclase decida qué aviso crear.
abstract class Canal {
protected abstract IAviso CrearAviso(); // el método fábrica
public void Notificar(string texto) {
IAviso aviso = CrearAviso(); // no sabe (ni le importa) qué clase concreta es
Console.WriteLine(aviso.Enviar(texto));
}
}
class CanalEmail : Canal { protected override IAviso CrearAviso() => new Email("ana@correo.es"); }
class CanalSms : Canal { protected override IAviso CrearAviso() => new Sms("600 111 222"); }
class CanalPush : Canal { protected override IAviso CrearAviso() => new Push(); }
class Program {
static void Main() {
var canales = new List<Canal> { new CanalEmail(), new CanalSms(), new CanalPush() };
foreach (Canal canal in canales) canal.Notificar("Tu pedido ha salido del almacén");
}
}<?php
/** El producto: lo que crea el método fábrica. */
interface Aviso {
public function enviar(string $texto): string;
}
class Email implements Aviso {
public function __construct(private string $direccion) {}
public function enviar(string $texto): string { return "Email a {$this->direccion}: $texto"; }
}
class Sms implements Aviso {
public function __construct(private string $telefono) {}
public function enviar(string $texto): string {
// mb_strlen y mb_substr cuentan caracteres, no bytes (la «é» ocupa dos)
$corto = mb_strlen($texto) > 20 ? mb_substr($texto, 0, 17) . "..." : $texto; // un SMS es corto
return "SMS a {$this->telefono}: $corto";
}
}
class Push implements Aviso {
public function enviar(string $texto): string { return "Notificación en el móvil: $texto"; }
}
/** El creador: tiene la lógica común y deja que cada subclase decida qué aviso crear. */
abstract class Canal {
abstract protected function crearAviso(): Aviso; // el método fábrica
public function notificar(string $texto): void {
$aviso = $this->crearAviso(); // no sabe (ni le importa) qué clase concreta es
echo $aviso->enviar($texto), "\n";
}
}
class CanalEmail extends Canal { protected function crearAviso(): Aviso { return new Email("ana@correo.es"); } }
class CanalSms extends Canal { protected function crearAviso(): Aviso { return new Sms("600 111 222"); } }
class CanalPush extends Canal { protected function crearAviso(): Aviso { return new Push(); } }
foreach ([new CanalEmail(), new CanalSms(), new CanalPush()] as $canal) {
$canal->notificar("Tu pedido ha salido del almacén");
}Salida al ejecutarlo (la misma en los 5 lenguajes)
Email a ana@correo.es: Tu pedido ha salido del almacén SMS a 600 111 222: Tu pedido ha sali... Notificación en el móvil: Tu pedido ha salido del almacén
La fábrica simple: un método estático
La variante que más se ve en el código real y en la biblioteca de Java.
1/** Muchas veces basta un método estático que decide la clase: la «fábrica simple». */
2interface Aviso {
3 String enviar(String texto);
4
5 static Aviso de(String canal) {
6 return switch (canal) {
7 case "email" -> new Email("ana@correo.es");
8 case "sms" -> new Sms("600 111 222");
9 case "push" -> new Push();
10 default -> throw new IllegalArgumentException("Canal desconocido: " + canal);
11 };
12 }
13}
14
15// Quien lo usa no escribe ningún new de una clase concreta:
16Aviso aviso = Aviso.de(preferencias.canal());
17
18// La biblioteca de Java está llena de métodos fábrica estáticos:
19List<String> ciclos = List.of("DAM", "DAW"); // devuelve una lista inmutable de una clase interna
20Integer n = Integer.valueOf(42); // puede devolver un objeto ya creado (caché)
21Calendar hoy = Calendar.getInstance(); // GregorianCalendar u otro, según el país
22ExecutorService pool = Executors.newFixedThreadPool(4);class Avisos:
"""La «fábrica simple»: el único sitio que sabe qué clase corresponde a cada canal."""
_FABRICAS = {
"email": lambda: Email("ana@correo.es"),
"sms": lambda: Sms("600 111 222"),
"push": Push, # una clase ya es una fábrica: se llama como una función
}
@staticmethod
def de(canal):
if canal not in Avisos._FABRICAS:
raise ValueError(f"Canal desconocido: {canal}")
return Avisos._FABRICAS[canal]()
aviso = Avisos.de(preferencias.canal)
# La biblioteca usa métodos de clase como constructores alternativos:
hoy = datetime.date.today()
fecha = datetime.date.fromisoformat("2026-10-11")
plazas = dict.fromkeys(["DAM", "DAW"], 30)// La «fábrica simple»: una función que decide qué clase crear
const FABRICAS = {
email: () => new Email("ana@correo.es"),
sms: () => new Sms("600 111 222"),
push: () => new Push(),
};
function crearAviso(canal) {
const fabrica = FABRICAS[canal];
if (!fabrica) throw new Error(`Canal desconocido: ${canal}`);
return fabrica();
}
const aviso = crearAviso(preferencias.canal);
// El navegador está lleno de métodos fábrica:
const boton = document.createElement("button"); // un HTMLButtonElement, sin escribir su clase
const letras = Array.from("DAM"); // ["D", "A", "M"]
const fecha = new Date(Date.parse("2026-10-11"));// La «fábrica simple»: un método estático que decide la clase
static class Avisos {
public static IAviso De(string canal) => canal switch {
"email" => new Email("ana@correo.es"),
"sms" => new Sms("600 111 222"),
"push" => new Push(),
_ => throw new ArgumentException(quot;Canal desconocido: {canal}"),
};
}
IAviso aviso = Avisos.De(preferencias.Canal);
// .NET también está lleno de ellos:
TimeSpan espera = TimeSpan.FromSeconds(30);
Guid id = Guid.NewGuid();
StreamWriter fichero = File.CreateText("notas.txt"); // devuelve un StreamWriter ya abierto<?php
// La «fábrica simple»: un método estático que decide la clase
final class Avisos {
public static function de(string $canal): Aviso {
return match ($canal) {
"email" => new Email("ana@correo.es"),
"sms" => new Sms("600 111 222"),
"push" => new Push(),
default => throw new InvalidArgumentException("Canal desconocido: $canal"),
};
}
}
$aviso = Avisos::de($preferencias["canal"]);
// PHP y sus frameworks también los usan mucho:
$fecha = DateTime::createFromFormat("d/m/Y", "11/10/2026");
$usuario = User::factory()->create(); // Laravel: factorías de modelos para las pruebasEn la práctica
List.of,Map.of,Integer.valueOf,Calendar.getInstance,Path.of,Executors.newFixedThreadPool: métodos fábrica estáticos de la biblioteca.DocumentBuilderFactory.newInstance().newDocumentBuilder()para leer XML yDriverManager.getConnectionen JDBC: devuelven la implementación que toca sin que escribas su clase.- En Android,
Fragment.newInstance(...)yIntent.createChooser(...); en Laravel y Spring, las factorías de modelos para las pruebas. - Los juegos crean enemigos, disparos o niveles con métodos fábrica para poder añadir tipos sin tocar el bucle principal.
Errores típicos
- Mover el
switchde losnewa la fábrica y después copiarlo en otros sitios: la fábrica tiene que ser el único que lo sabe. - Que el método fábrica devuelva la clase concreta (
Email crearAviso()) en vez de la interfaz: el código que lo usa vuelve a depender de ella. - Crear una jerarquía de creadores con una subclase por producto cuando una fábrica simple con un
Mapbastaba. - Devolver
nullcuando el tipo no existe: mejor lanzar una excepción con un mensaje claro (Canal desconocido: fax).
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. Fábrica de figuras
El programa lee figuras y escribe su área y su perímetro. Todo pasa por la fábrica Figuras.crear(linea), que de momento solo sabe crear círculos. Complétala para que cree rectángulos, cuadrados (que son un Rectangulo con los dos lados iguales: no hace falta otra clase) y triángulos, y para que rechace las líneas que no valen. El main y las figuras ya están escritos.
- Cada línea:
circulo r,rectangulo b h,cuadrado lotriangulo a b c. Salida:nombre: área A, perímetro Pcon dos decimales y coma. Al final,Total: N figuras, área T. - Errores (los mensajes los da la fábrica con una
IllegalArgumentException):Figura desconocida: tipo,Faltan o sobran medidas: línea,Medida no válida: línea(no es un número o no es mayor que 0) yTriángulo imposible: línea(un lado es mayor o igual que la suma de los otros dos).
Ejemplo
circulo 2 rectangulo 3 4 cuadrado 2.5 triangulo 3 4 5
círculo de radio 2: área 12,57, perímetro 12,57 rectángulo 3 × 4: área 12,00, perímetro 14,00 rectángulo 2,5 × 2,5: área 6,25, perímetro 10,00 triángulo 3, 4, 5: área 6,00, perímetro 12,00 Total: 4 figuras, área 36,82
Ver la solución explicada
1import java.math.BigDecimal;
2import java.util.*;
3
4interface Figura {
5 String nombre();
6 double area();
7 double perimetro();
8}
9
10record Circulo(double r) implements Figura {
11 public String nombre() { return "círculo de radio " + Main.num(r); }
12 public double area() { return Math.PI * r * r; }
13 public double perimetro() { return 2 * Math.PI * r; }
14}
15
16record Rectangulo(double base, double altura) implements Figura {
17 public String nombre() { return "rectángulo " + Main.num(base) + " × " + Main.num(altura); }
18 public double area() { return base * altura; }
19 public double perimetro() { return 2 * (base + altura); }
20}
21
22record Triangulo(double a, double b, double c) implements Figura {
23 public String nombre() { return "triángulo " + Main.num(a) + ", " + Main.num(b) + ", " + Main.num(c); }
24 public double area() {
25 double s = perimetro() / 2; // fórmula de Herón
26 return Math.sqrt(s * (s - a) * (s - b) * (s - c));
27 }
28 public double perimetro() { return a + b + c; }
29}
30
31/** La fábrica: el único sitio del programa que sabe qué clase corresponde a cada nombre. */
32class Figuras {
33 static Figura crear(String linea) {
34 String[] p = linea.trim().split("\\s+");
35 int n = switch (p[0]) {
36 case "circulo", "cuadrado" -> 1;
37 case "rectangulo" -> 2;
38 case "triangulo" -> 3;
39 default -> throw new IllegalArgumentException("Figura desconocida: " + p[0]);
40 };
41 if (p.length - 1 != n) throw new IllegalArgumentException("Faltan o sobran medidas: " + linea.trim());
42 double[] m = new double[n];
43 for (int i = 0; i < n; i++) {
44 try {
45 m[i] = Double.parseDouble(p[i + 1]);
46 } catch (NumberFormatException e) {
47 throw new IllegalArgumentException("Medida no válida: " + linea.trim());
48 }
49 if (!(m[i] > 0) || Double.isInfinite(m[i])) throw new IllegalArgumentException("Medida no válida: " + linea.trim());
50 }
51 return switch (p[0]) {
52 case "circulo" -> new Circulo(m[0]);
53 case "cuadrado" -> new Rectangulo(m[0], m[0]); // no hace falta una clase nueva
54 case "rectangulo" -> new Rectangulo(m[0], m[1]);
55 default -> {
56 double a = m[0], b = m[1], c = m[2];
57 if (a + b <= c || a + c <= b || b + c <= a) throw new IllegalArgumentException("Triángulo imposible: " + linea.trim());
58 yield new Triangulo(a, b, c);
59 }
60 };
61 }
62}
63
64public class Main {
65 static String num(double x) {
66 return new BigDecimal(Double.toString(x)).stripTrailingZeros().toPlainString().replace('.', ',');
67 }
68
69 static String dos(double x) {
70 return String.format(Locale.ROOT, "%.2f", x).replace('.', ',');
71 }
72
73 public static void main(String[] args) {
74 Scanner sc = new Scanner(System.in);
75 List<Figura> figuras = new ArrayList<>();
76 while (sc.hasNextLine()) {
77 String linea = sc.nextLine();
78 if (linea.isBlank()) continue;
79 try {
80 Figura f = Figuras.crear(linea);
81 figuras.add(f);
82 System.out.println(f.nombre() + ": área " + dos(f.area()) + ", perímetro " + dos(f.perimetro()));
83 } catch (IllegalArgumentException e) {
84 System.out.println(e.getMessage());
85 }
86 }
87 double total = 0;
88 for (Figura f : figuras) total += f.area();
89 System.out.println("Total: " + figuras.size() + (figuras.size() == 1 ? " figura" : " figuras") + ", área " + dos(total));
90 }
91}El main no tiene ni un new de una figura: si mañana hay hexágonos, solo cambia la fábrica.
La fábrica también es el sitio natural para validar: ningún objeto Triangulo imposible llega a existir.
2. Exportar una tabla en varios formatos
Exportador.exportar escribe una tabla siempre igual: cabecera, filas y pie. Lo que cambia es el Formato, que cada exportador crea con su método fábrica crearFormato. Ya está el CSV: escribe los formatos Markdown y Json con sus exportadores y regístralos en el main con las claves markdown y json.
- Órdenes:
columnas a;b;c(empieza una tabla nueva),fila x;y;z(tantos valores como columnas, oFila no válida: x;y),exportar formato(escribe--- formato ---y la tabla,Formato desconocido: pdfoFaltan las columnas). - Markdown:
| a | b |, después| --- | --- |, una línea así por fila, con cada|de un valor escrito como\|, y al final_N filas_(_1 fila_). - JSON:
[, cada fila en su línea como{"columna": "valor", ...}separadas por coma, y]. En los textos,\se escribe\\y"se escribe\". Una tabla sin filas es[y].
Ejemplo
columnas nombre;ciclo;nota fila Ana;DAM;8.5 fila Luis;DAW;6 exportar csv exportar markdown exportar json
--- csv ---
nombre;ciclo;nota
Ana;DAM;8.5
Luis;DAW;6
--- markdown ---
| nombre | ciclo | nota |
| --- | --- | --- |
| Ana | DAM | 8.5 |
| Luis | DAW | 6 |
_2 filas_
--- json ---
[
{"nombre": "Ana", "ciclo": "DAM", "nota": "8.5"},
{"nombre": "Luis", "ciclo": "DAW", "nota": "6"}
]Ver la solución explicada
1import java.util.*;
2
3/** El producto: escribe una tabla en un formato. Se crea uno nuevo para cada exportación. */
4interface Formato {
5 String cabecera(List<String> columnas);
6 String fila(List<String> valores);
7 String pie(int filas);
8}
9
10/** El creador: el algoritmo de exportar es siempre el mismo; el formato lo decide cada subclase. */
11abstract class Exportador {
12 protected abstract Formato crearFormato(); // el método fábrica
13
14 final String exportar(List<String> columnas, List<List<String>> filas) {
15 Formato f = crearFormato();
16 StringBuilder sb = new StringBuilder(f.cabecera(columnas));
17 for (List<String> fila : filas) sb.append(f.fila(fila));
18 sb.append(f.pie(filas.size()));
19 return sb.toString();
20 }
21}
22
23class Csv implements Formato {
24 private static String celda(String v) {
25 return v.contains("\"") ? "\"" + v.replace("\"", "\"\"") + "\"" : v;
26 }
27 public String cabecera(List<String> columnas) { return fila(columnas); }
28 public String fila(List<String> valores) {
29 List<String> celdas = new ArrayList<>();
30 for (String v : valores) celdas.add(celda(v));
31 return String.join(";", celdas) + "\n";
32 }
33 public String pie(int filas) { return ""; }
34}
35
36class ExportadorCsv extends Exportador {
37 protected Formato crearFormato() { return new Csv(); }
38}
39
40class Markdown implements Formato {
41 private static String celda(String v) { return v.replace("|", "\\|"); }
42
43 private static String linea(List<String> valores) {
44 StringBuilder sb = new StringBuilder("|");
45 for (String v : valores) sb.append(" ").append(celda(v)).append(" |");
46 return sb.append("\n").toString();
47 }
48
49 public String cabecera(List<String> columnas) {
50 return linea(columnas) + "|" + " --- |".repeat(columnas.size()) + "\n";
51 }
52 public String fila(List<String> valores) { return linea(valores); }
53 public String pie(int filas) { return "_" + filas + (filas == 1 ? " fila_" : " filas_") + "\n"; }
54}
55
56/** Guarda las columnas y si ya ha escrito alguna fila: por eso cada exportación usa un formato nuevo. */
57class Json implements Formato {
58 private List<String> columnas;
59 private boolean primera = true;
60
61 private static String texto(String v) {
62 return "\"" + v.replace("\\", "\\\\").replace("\"", "\\\"") + "\"";
63 }
64
65 public String cabecera(List<String> columnas) {
66 this.columnas = columnas;
67 return "[\n";
68 }
69 public String fila(List<String> valores) {
70 List<String> pares = new ArrayList<>();
71 for (int i = 0; i < columnas.size(); i++) pares.add(texto(columnas.get(i)) + ": " + texto(valores.get(i)));
72 String s = (primera ? "" : ",\n") + " {" + String.join(", ", pares) + "}";
73 primera = false;
74 return s;
75 }
76 public String pie(int filas) { return (filas > 0 ? "\n" : "") + "]\n"; }
77}
78
79class ExportadorMarkdown extends Exportador {
80 protected Formato crearFormato() { return new Markdown(); }
81}
82
83class ExportadorJson extends Exportador {
84 protected Formato crearFormato() { return new Json(); }
85}
86
87public class Main {
88 public static void main(String[] args) {
89 Map<String, Exportador> exportadores = new LinkedHashMap<>();
90 exportadores.put("csv", new ExportadorCsv());
91 exportadores.put("markdown", new ExportadorMarkdown());
92 exportadores.put("json", new ExportadorJson());
93
94 Scanner sc = new Scanner(System.in);
95 List<String> columnas = null;
96 List<List<String>> filas = new ArrayList<>();
97 while (sc.hasNextLine()) {
98 String linea = sc.nextLine().trim();
99 if (linea.isEmpty()) continue;
100 int esp = linea.indexOf(' ');
101 String orden = esp < 0 ? linea : linea.substring(0, esp);
102 String resto = esp < 0 ? "" : linea.substring(esp + 1).trim();
103 switch (orden) {
104 case "columnas" -> {
105 columnas = List.of(resto.split(";", -1));
106 filas.clear();
107 }
108 case "fila" -> {
109 List<String> valores = List.of(resto.split(";", -1));
110 if (columnas == null || valores.size() != columnas.size()) System.out.println("Fila no válida: " + resto);
111 else filas.add(valores);
112 }
113 case "exportar" -> {
114 Exportador e = exportadores.get(resto);
115 if (e == null) System.out.println("Formato desconocido: " + resto);
116 else if (columnas == null) System.out.println("Faltan las columnas");
117 else {
118 System.out.println("--- " + resto + " ---");
119 System.out.print(e.exportar(columnas, filas));
120 }
121 }
122 default -> System.out.println("Orden no válida: " + linea);
123 }
124 }
125 }
126}exportar está escrito una vez y es final: los formatos solo rellenan sus tres piezas. Esa forma (un algoritmo fijo que llama a pasos que cambian) es también el patrón Template Method.
Que el método fábrica cree un objeto nuevo en cada llamada permite que el formato guarde estado (la coma del JSON) sin que una exportación estropee la siguiente.
Test
Test: Factory Method
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é devuelve el método fábrica?
2.En la versión de los libros, ¿quién decide qué producto concreto se crea?
3.¿Cuál de estos es un método fábrica estático de la biblioteca de Java?
4.Quieres añadir avisos por Telegram al ejemplo de los canales. ¿Qué cambias?
5.¿Qué ventaja tiene validar dentro de la fábrica?