Patrón Facade
Una clase que da una puerta sencilla a un subsistema complicado: matricular, comprar o encender el home cinema es una llamada, y la fachada coordina por dentro las clases que hagan falta en su orden.
nivel básicoTambién: fachada, capa de servicio, servicio
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.
Facade
Escribe las solicitudes de matrícula separadas por comas: nombre, DNI y ciclo (DAM, DAW o ASIR). Quedan 2 plazas en DAM, 1 en DAW y ninguna en ASIR.
Paso 1
La fachada tiene un método por operación (matricular) y conoce las cuatro clases del subsistema. El cliente solo la conoce a ella.
1boolean matricular(String nombre, String dni, String ciclo) { // solicitudes = 3
2 if (!validador.dniValido(dni)) return false;
3 if (!plazas.reservar(ciclo)) return false;
4 String exp = expedientes.crear(nombre, dni, ciclo);
5 correo.enviar(nombre, "matriculado con " + exp);
6 return true;
7}Variables
- solicitudes
- 3
Atajos con el foco dentro del visualizador: ← → paso a paso, Espacio reproducir o pausar, Inicio/Fin ir al principio o al final.
La idea
Matricular a un alumno exige validar su DNI, reservar plaza, crear el expediente y enviarle un correo, en ese orden y parando si algo falla. Si cada pantalla o cada controlador que matricula hace esos pasos por su cuenta, el código se repite, el orden se equivoca y todas las pantallas dependen de las cuatro clases.
El patrón Facade pone delante del subsistema una clase con métodos de alto nivel (matricular(nombre, dni, ciclo)). La fachada conoce las clases de dentro y las coordina; el resto del programa solo conoce la fachada. Las clases del subsistema siguen ahí y se pueden usar directamente si hace falta algo especial: la fachada simplifica, no esconde a la fuerza.
En las aplicaciones por capas, la capa de servicio es una fachada: el controlador llama a pedidoService.comprar(...) y no sabe nada del DAO, de la pasarela de pago ni del correo. Files.readAllLines es otra: lee un fichero entero sin que abras flujos ni búferes.
Cuándo usarlo
- Una operación habitual necesita varias clases de un subsistema en un orden concreto.
- Quieres que el resto del programa dependa de una sola clase y no de todas las del subsistema (para poder cambiarlas sin tocar nada más).
- Quieres organizar una aplicación en capas: la de presentación habla con los servicios, no con el acceso a datos.
Cuándo no
- Si la fachada acaba teniendo un método por cada método del subsistema: no simplifica nada.
- Si se convierte en una «clase dios» que lo hace todo: mejor varias fachadas pequeñas, una por área.
Participantes
- Fachada. La clase con las operaciones de alto nivel (
SecretariaFacade.matricular), que delega en el subsistema. - Subsistema. Las clases que hacen el trabajo (
Validador,Plazas,Expedientes,Correo). No saben que hay una fachada. - Cliente. Usa la fachada en lugar de coordinar el subsistema por su cuenta.
Diagrama de clases
Ver el diagrama en PlantUML
1@startuml
2class SecretariaFacade {
3 +matricular(nombre : String, dni : String, ciclo : String) : boolean
4}
5class Validador {
6 +dniValido(dni : String) : boolean
7}
8class Plazas {
9 +reservar(ciclo : String) : boolean
10}
11class Expedientes {
12 +crear(nombre : String, dni : String, ciclo : String) : String
13}
14class Correo {
15 +enviar(destino : String, texto : String) : void
16}
17class Cliente
18SecretariaFacade --> Validador
19SecretariaFacade --> Plazas
20SecretariaFacade --> Expedientes
21SecretariaFacade --> Correo
22Cliente ..> SecretariaFacade : usa
23@endumlPuedes copiarlo en el editor de diagramas UML y modificarlo.
El código
La secretaría de un instituto
El main hace tres matrículas con una llamada cada una; la fachada valida, reserva, crea el expediente y avisa, y se para en el primer paso que falla.
1import java.util.*;
2
3// ───── El subsistema: varias clases, cada una con su trabajo ─────
4class Validador {
5 boolean dniValido(String dni) {
6 if (!dni.matches("\\d{8}[A-Z]")) return false;
7 return "TRWAGMYFPDXBNJZSQVHLCKE".charAt(Integer.parseInt(dni.substring(0, 8)) % 23) == dni.charAt(8);
8 }
9}
10
11class Plazas {
12 private final Map<String, Integer> libres = new HashMap<>(Map.of("DAM", 2, "DAW", 1));
13 boolean reservar(String ciclo) {
14 int quedan = libres.getOrDefault(ciclo, 0);
15 if (quedan == 0) return false;
16 libres.put(ciclo, quedan - 1);
17 return true;
18 }
19}
20
21class Expedientes {
22 private int siguiente = 1;
23 String crear(String nombre, String dni, String ciclo) {
24 return String.format("EXP-%03d", siguiente++);
25 }
26}
27
28class Correo {
29 void enviar(String destino, String texto) { System.out.println(" Correo a " + destino + ": " + texto); }
30}
31
32/** La fachada: una sola puerta, sencilla, para una operación que usa todo el subsistema. */
33class SecretariaFacade {
34 private final Validador validador = new Validador();
35 private final Plazas plazas = new Plazas();
36 private final Expedientes expedientes = new Expedientes();
37 private final Correo correo = new Correo();
38
39 boolean matricular(String nombre, String dni, String ciclo) {
40 if (!validador.dniValido(dni)) {
41 System.out.println(" DNI no válido: " + dni);
42 return false;
43 }
44 if (!plazas.reservar(ciclo)) {
45 System.out.println(" No quedan plazas en " + ciclo);
46 return false;
47 }
48 String expediente = expedientes.crear(nombre, dni, ciclo);
49 correo.enviar(nombre, "matriculado en " + ciclo + " con el expediente " + expediente);
50 return true;
51 }
52}
53
54public class Main {
55 public static void main(String[] args) {
56 SecretariaFacade secretaria = new SecretariaFacade(); // el cliente solo conoce la fachada
57 String[][] solicitudes = {{"Ana", "12345678Z", "DAW"}, {"Luis", "12345678A", "DAM"}, {"Marta", "00000000T", "DAW"}};
58 for (String[] s : solicitudes) {
59 System.out.println("Matrícula de " + s[0] + ":");
60 System.out.println(secretaria.matricular(s[0], s[1], s[2]) ? " Hecha" : " Rechazada");
61 }
62 }
63}import re
# ───── El subsistema: varias clases, cada una con su trabajo ─────
class Validador:
def dni_valido(self, dni):
if not re.fullmatch(r"\d{8}[A-Z]", dni):
return False
return "TRWAGMYFPDXBNJZSQVHLCKE"[int(dni[:8]) % 23] == dni[8]
class Plazas:
def __init__(self):
self._libres = {"DAM": 2, "DAW": 1}
def reservar(self, ciclo):
if self._libres.get(ciclo, 0) == 0:
return False
self._libres[ciclo] -= 1
return True
class Expedientes:
def __init__(self):
self._siguiente = 1
def crear(self, nombre, dni, ciclo):
numero = f"EXP-{self._siguiente:03d}"
self._siguiente += 1
return numero
class Correo:
def enviar(self, destino, texto):
print(f" Correo a {destino}: {texto}")
class SecretariaFacade:
"""La fachada: una sola puerta, sencilla, para una operación que usa todo el subsistema."""
def __init__(self):
self._validador = Validador()
self._plazas = Plazas()
self._expedientes = Expedientes()
self._correo = Correo()
def matricular(self, nombre, dni, ciclo):
if not self._validador.dni_valido(dni):
print(f" DNI no válido: {dni}")
return False
if not self._plazas.reservar(ciclo):
print(f" No quedan plazas en {ciclo}")
return False
expediente = self._expedientes.crear(nombre, dni, ciclo)
self._correo.enviar(nombre, f"matriculado en {ciclo} con el expediente {expediente}")
return True
secretaria = SecretariaFacade() # el cliente solo conoce la fachada
for nombre, dni, ciclo in [("Ana", "12345678Z", "DAW"), ("Luis", "12345678A", "DAM"), ("Marta", "00000000T", "DAW")]:
print(f"Matrícula de {nombre}:")
print(" Hecha" if secretaria.matricular(nombre, dni, ciclo) else " Rechazada")// ───── El subsistema: varias clases, cada una con su trabajo ─────
class Validador {
dniValido(dni) {
if (!/^\d{8}[A-Z]$/.test(dni)) return false;
return "TRWAGMYFPDXBNJZSQVHLCKE"[Number(dni.slice(0, 8)) % 23] === dni[8];
}
}
class Plazas {
#libres = new Map([["DAM", 2], ["DAW", 1]]);
reservar(ciclo) {
const quedan = this.#libres.get(ciclo) ?? 0;
if (quedan === 0) return false;
this.#libres.set(ciclo, quedan - 1);
return true;
}
}
class Expedientes {
#siguiente = 1;
crear(nombre, dni, ciclo) { return `EXP-${String(this.#siguiente++).padStart(3, "0")}`; }
}
class Correo {
enviar(destino, texto) { console.log(` Correo a ${destino}: ${texto}`); }
}
/** La fachada: una sola puerta, sencilla, para una operación que usa todo el subsistema. */
class SecretariaFacade {
#validador = new Validador();
#plazas = new Plazas();
#expedientes = new Expedientes();
#correo = new Correo();
matricular(nombre, dni, ciclo) {
if (!this.#validador.dniValido(dni)) {
console.log(` DNI no válido: ${dni}`);
return false;
}
if (!this.#plazas.reservar(ciclo)) {
console.log(` No quedan plazas en ${ciclo}`);
return false;
}
const expediente = this.#expedientes.crear(nombre, dni, ciclo);
this.#correo.enviar(nombre, `matriculado en ${ciclo} con el expediente ${expediente}`);
return true;
}
}
const secretaria = new SecretariaFacade(); // el cliente solo conoce la fachada
for (const [nombre, dni, ciclo] of [["Ana", "12345678Z", "DAW"], ["Luis", "12345678A", "DAM"], ["Marta", "00000000T", "DAW"]]) {
console.log(`Matrícula de ${nombre}:`);
console.log(secretaria.matricular(nombre, dni, ciclo) ? " Hecha" : " Rechazada");
}using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
// ───── El subsistema: varias clases, cada una con su trabajo ─────
class Validador {
public bool DniValido(string dni) {
if (!Regex.IsMatch(dni, @"^\d{8}[A-Z]quot;)) return false;
return "TRWAGMYFPDXBNJZSQVHLCKE"[int.Parse(dni[..8]) % 23] == dni[8];
}
}
class Plazas {
private readonly Dictionary<string, int> libres = new() { ["DAM"] = 2, ["DAW"] = 1 };
public bool Reservar(string ciclo) {
int quedan = libres.GetValueOrDefault(ciclo, 0);
if (quedan == 0) return false;
libres[ciclo] = quedan - 1;
return true;
}
}
class Expedientes {
private int siguiente = 1;
public string Crear(string nombre, string dni, string ciclo) => quot;EXP-{siguiente++:000}";
}
class Correo {
public void Enviar(string destino, string texto) => Console.WriteLine(quot; Correo a {destino}: {texto}");
}
// La fachada: una sola puerta, sencilla, para una operación que usa todo el subsistema.
class SecretariaFacade {
private readonly Validador validador = new();
private readonly Plazas plazas = new();
private readonly Expedientes expedientes = new();
private readonly Correo correo = new();
public bool Matricular(string nombre, string dni, string ciclo) {
if (!validador.DniValido(dni)) {
Console.WriteLine(quot; DNI no válido: {dni}");
return false;
}
if (!plazas.Reservar(ciclo)) {
Console.WriteLine(quot; No quedan plazas en {ciclo}");
return false;
}
string expediente = expedientes.Crear(nombre, dni, ciclo);
correo.Enviar(nombre, quot;matriculado en {ciclo} con el expediente {expediente}");
return true;
}
}
class Program {
static void Main() {
var secretaria = new SecretariaFacade(); // el cliente solo conoce la fachada
var solicitudes = new[] { ("Ana", "12345678Z", "DAW"), ("Luis", "12345678A", "DAM"), ("Marta", "00000000T", "DAW") };
foreach (var (nombre, dni, ciclo) in solicitudes) {
Console.WriteLine(quot;Matrícula de {nombre}:");
Console.WriteLine(secretaria.Matricular(nombre, dni, ciclo) ? " Hecha" : " Rechazada");
}
}
}<?php
// ───── El subsistema: varias clases, cada una con su trabajo ─────
class Validador {
public function dniValido(string $dni): bool {
if (!preg_match('/^\d{8}[A-Z]$/', $dni)) return false;
return "TRWAGMYFPDXBNJZSQVHLCKE"[intval(substr($dni, 0, 8)) % 23] === $dni[8];
}
}
class Plazas {
private array $libres = ["DAM" => 2, "DAW" => 1];
public function reservar(string $ciclo): bool {
$quedan = $this->libres[$ciclo] ?? 0;
if ($quedan === 0) return false;
$this->libres[$ciclo] = $quedan - 1;
return true;
}
}
class Expedientes {
private int $siguiente = 1;
public function crear(string $nombre, string $dni, string $ciclo): string {
return sprintf("EXP-%03d", $this->siguiente++);
}
}
class Correo {
public function enviar(string $destino, string $texto): void { echo " Correo a $destino: $texto\n"; }
}
/** La fachada: una sola puerta, sencilla, para una operación que usa todo el subsistema. */
class SecretariaFacade {
private Validador $validador;
private Plazas $plazas;
private Expedientes $expedientes;
private Correo $correo;
public function __construct() {
$this->validador = new Validador();
$this->plazas = new Plazas();
$this->expedientes = new Expedientes();
$this->correo = new Correo();
}
public function matricular(string $nombre, string $dni, string $ciclo): bool {
if (!$this->validador->dniValido($dni)) {
echo " DNI no válido: $dni\n";
return false;
}
if (!$this->plazas->reservar($ciclo)) {
echo " No quedan plazas en $ciclo\n";
return false;
}
$expediente = $this->expedientes->crear($nombre, $dni, $ciclo);
$this->correo->enviar($nombre, "matriculado en $ciclo con el expediente $expediente");
return true;
}
}
$secretaria = new SecretariaFacade(); // el cliente solo conoce la fachada
foreach ([["Ana", "12345678Z", "DAW"], ["Luis", "12345678A", "DAM"], ["Marta", "00000000T", "DAW"]] as [$nombre, $dni, $ciclo]) {
echo "Matrícula de $nombre:\n";
echo $secretaria->matricular($nombre, $dni, $ciclo) ? " Hecha" : " Rechazada", "\n";
}Salida al ejecutarlo (la misma en los 5 lenguajes)
Matrícula de Ana: Correo a Ana: matriculado en DAW con el expediente EXP-001 Hecha Matrícula de Luis: DNI no válido: 12345678A Rechazada Matrícula de Marta: No quedan plazas en DAW Rechazada
Fachadas que ya usas
Files frente a abrir los flujos a mano, y la capa de servicio de una aplicación.
1// Files es una fachada: lee un fichero entero sin abrir flujos, búferes ni decodificadores a mano
2List<String> lineas = Files.readAllLines(Path.of("notas.csv"));
3Files.writeString(Path.of("informe.txt"), "Hecho");
4
5// Lo mismo, sin la fachada:
6try (BufferedReader lector = new BufferedReader(new InputStreamReader(
7 new FileInputStream("notas.csv"), StandardCharsets.UTF_8))) {
8 List<String> todas = new ArrayList<>();
9 String l;
10 while ((l = lector.readLine()) != null) todas.add(l);
11}
12
13// En una aplicación, la capa de servicio suele ser una fachada: el controlador llama a
14// pedidoService.comprar(...) y no conoce el DAO, la pasarela de pago ni el correo.from pathlib import Path
# Path es una fachada: lee o escribe un fichero entero en una línea
lineas = Path("notas.csv").read_text(encoding="utf-8").splitlines()
Path("informe.txt").write_text("Hecho", encoding="utf-8")
# requests es una fachada sobre http.client, sockets, SSL, cookies y redirecciones
import requests
alumnos = requests.get("https://api.ejemplo.com/alumnos", timeout=10).json()// fetch es una fachada sobre XMLHttpRequest: una llamada en lugar de configurar el objeto paso a paso
const alumnos = await (await fetch("/api/alumnos")).json();
// Sin la fachada:
const xhr = new XMLHttpRequest();
xhr.open("GET", "/api/alumnos");
xhr.responseType = "json";
xhr.onload = () => console.log(xhr.response);
xhr.send();// File es una fachada: lee o escribe un fichero entero sin abrir flujos a mano
string[] lineas = File.ReadAllLines("notas.csv");
File.WriteAllText("informe.txt", "Hecho");
// En ASP.NET Core, el controlador llama a un servicio (la fachada) que coordina
// repositorios, correo y pagos: public IActionResult Comprar(...) => Ok(_pedidos.Comprar(...));<?php
// file_get_contents es una fachada: abre, lee y cierra en una llamada (también URLs)
$lineas = file("notas.csv", FILE_IGNORE_NEW_LINES);
file_put_contents("informe.txt", "Hecho");
// En Laravel las «facades» son otra cosa (accesos estáticos al contenedor), pero los servicios
// de una aplicación son fachadas: el controlador llama a $pedidos->comprar(...) y nada más.En la práctica
- La capa de servicio de Spring, Laravel o ASP.NET: cada servicio es una fachada sobre repositorios, correo, pagos…
Files.readAllLines,Files.writeStringyFiles.copyen Java;fetchen el navegador frente aXMLHttpRequest.- Las bibliotecas que envuelven una API compleja en unas pocas funciones (un SDK de pagos,
JdbcTemplatesobre JDBC). - En Android, un repositorio que decide si los datos salen de la red o de la base de datos local es una fachada para las pantallas.
Errores típicos
- Meter en la fachada la lógica que pertenece a las clases del subsistema: debe coordinar, no hacer su trabajo.
- Una fachada gigante con decenas de métodos de todas las áreas: se convierte en una clase imposible de mantener.
- No respetar el orden de los pasos ni deshacer lo hecho cuando uno falla (cobrar y no tener stock).
- Obligar a todo el mundo a pasar por la fachada incluso para casos especiales: el subsistema sigue disponible.
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. El mando del home cinema
Ver una película exige atenuar las luces, encender el proyector y elegir su entrada, encender el amplificador y ponerle volumen, y reproducir. Escribe la fachada HomeCinema, con verPelicula, volumen y terminar, que haga cada operación con una sola llamada y en el orden indicado.
- Órdenes:
ver título,volumen nyterminar. Los aparatos ya escriben lo que hacen. ver: luces al 10 %, proyector encendido, entradaHDMI1, amplificador encendido, volumen (20 al empezar, o el último que se fijó) y reproducir. Si ya hay una película:Ya se está viendo «título».volumen n(de 0 a 50, si noVolumen fuera de rango: n): se guarda y, si hay película, se aplica al amplificador.terminar: parar, apagar amplificador, apagar proyector y luces al 100 %; sin película,No hay ninguna película en marcha.
Ejemplo
ver Dune volumen 30 terminar
Luces al 10 % Proyector encendido Proyector: entrada HDMI1 Amplificador encendido Amplificador: volumen 20 Reproduciendo «Dune» Amplificador: volumen 30 Parado «Dune» Amplificador apagado Proyector apagado Luces al 100 %
Ver la solución explicada
1import java.util.*;
2
3// ───── El subsistema del home cinema ─────
4class Proyector {
5 boolean encendido;
6 void encender() { encendido = true; System.out.println("Proyector encendido"); }
7 void apagar() { encendido = false; System.out.println("Proyector apagado"); }
8 void entrada(String fuente) { System.out.println("Proyector: entrada " + fuente); }
9}
10
11class Amplificador {
12 void encender() { System.out.println("Amplificador encendido"); }
13 void apagar() { System.out.println("Amplificador apagado"); }
14 void volumen(int v) { System.out.println("Amplificador: volumen " + v); }
15}
16
17class Luces {
18 void atenuar(int porcentaje) { System.out.println("Luces al " + porcentaje + " %"); }
19}
20
21class Reproductor {
22 String titulo;
23 void reproducir(String titulo) { this.titulo = titulo; System.out.println("Reproduciendo «" + titulo + "»"); }
24 void parar() { System.out.println("Parado «" + titulo + "»"); titulo = null; }
25}
26
27/** La fachada: dos órdenes sencillas que coordinan los cuatro aparatos en el orden correcto. */
28class HomeCinema {
29 private final Proyector proyector = new Proyector();
30 private final Amplificador amplificador = new Amplificador();
31 private final Luces luces = new Luces();
32 private final Reproductor reproductor = new Reproductor();
33 private int volumen = 20;
34
35 void verPelicula(String titulo) {
36 if (reproductor.titulo != null) {
37 System.out.println("Ya se está viendo «" + reproductor.titulo + "»");
38 return;
39 }
40 luces.atenuar(10);
41 proyector.encender();
42 proyector.entrada("HDMI1");
43 amplificador.encender();
44 amplificador.volumen(volumen);
45 reproductor.reproducir(titulo);
46 }
47
48 void volumen(int v) {
49 if (v < 0 || v > 50) {
50 System.out.println("Volumen fuera de rango: " + v);
51 return;
52 }
53 volumen = v;
54 if (reproductor.titulo != null) amplificador.volumen(v);
55 }
56
57 void terminar() {
58 if (reproductor.titulo == null) {
59 System.out.println("No hay ninguna película en marcha");
60 return;
61 }
62 reproductor.parar();
63 amplificador.apagar();
64 proyector.apagar();
65 luces.atenuar(100);
66 }
67}
68
69public class Main {
70 public static void main(String[] args) {
71 HomeCinema cine = new HomeCinema();
72 Scanner sc = new Scanner(System.in);
73 while (sc.hasNextLine()) {
74 String linea = sc.nextLine().trim();
75 if (linea.isEmpty()) continue;
76 String[] p = linea.split("\\s+", 2);
77 try {
78 switch (p[0]) {
79 case "ver" -> cine.verPelicula(p[1]);
80 case "volumen" -> cine.volumen(Integer.parseInt(p[1]));
81 case "terminar" -> cine.terminar();
82 default -> System.out.println("Orden no válida: " + linea);
83 }
84 } catch (RuntimeException e) {
85 System.out.println("Orden no válida: " + linea);
86 }
87 }
88 }
89}El main da órdenes de alto nivel; las seis llamadas a los aparatos, y su orden, están en un solo sitio.
Si se cambia el proyector por otro modelo, solo se toca la fachada.
2. Comprar en la tienda online
Comprar toca cuatro subsistemas: inventario, envíos, pasarela de pago y facturas. Escribe la fachada Tienda con comprar y stock, que coordine los pasos en el orden correcto y sin dejar nada a medias: si el pago se rechaza, el stock no debe cambiar.
- Órdenes:
comprar producto cantidad tarjeta provinciaystock producto. Productos:portatil,ratonymonitor. - Comprobaciones, en orden:
Producto desconocido: x,Cantidad no válida: n(menor que 1),Sin stock suficiente de x (quedan n)y, tras calcular el total (importe + envío),Pago rechazado: total. - Si todo va bien: se retira el stock y
Factura F-0001: n × producto importe + envío E = total, con los importes en euros con coma.stockescribeStock de x: n.
Ejemplo
comprar raton 2 VISA1 Madrid comprar portatil 1 VISA1 Canarias comprar raton 1 VISA2 Baleares stock raton stock portatil
Factura F-0001: 2 × raton 39,80 € + envío 4,95 € = 44,75 € Factura F-0002: 1 × portatil 799,00 € + envío 0,00 € = 799,00 € Factura F-0003: 1 × raton 19,90 € + envío 15,00 € = 34,90 € Stock de raton: 7 Stock de portatil: 2
Ver la solución explicada
1import java.util.*;
2
3// ───── El subsistema de la tienda ─────
4class Inventario {
5 private final Map<String, Integer> stock = new HashMap<>(Map.of("portatil", 3, "raton", 10, "monitor", 1));
6 private final Map<String, Double> precios = Map.of("portatil", 799.0, "raton", 19.9, "monitor", 189.0);
7 boolean existe(String producto) { return precios.containsKey(producto); }
8 double precio(String producto) { return precios.get(producto); }
9 boolean hay(String producto, int cantidad) { return stock.get(producto) >= cantidad; }
10 void retirar(String producto, int cantidad) { stock.merge(producto, -cantidad, Integer::sum); }
11 int stock(String producto) { return stock.get(producto); }
12}
13
14class Envios {
15 double coste(String provincia, double importe) {
16 if (importe >= 100) return 0;
17 return provincia.equals("Canarias") || provincia.equals("Baleares") ? 15 : 4.95;
18 }
19}
20
21class Pasarela {
22 private final Map<String, Double> saldo = new HashMap<>(Map.of("VISA1", 2000.0, "VISA2", 50.0));
23 boolean cobrar(String tarjeta, double importe) {
24 double s = saldo.getOrDefault(tarjeta, -1.0);
25 if (s < importe) return false;
26 saldo.put(tarjeta, s - importe);
27 return true;
28 }
29}
30
31class Facturas {
32 private int n = 0;
33 String emitir() { return String.format("F-%04d", ++n); }
34}
35
36/** La fachada: comprar es una sola llamada, aunque toque cuatro subsistemas en un orden concreto. */
37class Tienda {
38 private final Inventario inventario = new Inventario();
39 private final Envios envios = new Envios();
40 private final Pasarela pasarela = new Pasarela();
41 private final Facturas facturas = new Facturas();
42
43 String comprar(String producto, int cantidad, String tarjeta, String provincia) {
44 if (!inventario.existe(producto)) return "Producto desconocido: " + producto;
45 if (cantidad < 1) return "Cantidad no válida: " + cantidad;
46 if (!inventario.hay(producto, cantidad)) return "Sin stock suficiente de " + producto + " (quedan " + inventario.stock(producto) + ")";
47 double importe = inventario.precio(producto) * cantidad;
48 double envio = envios.coste(provincia, importe);
49 double total = importe + envio;
50 if (!pasarela.cobrar(tarjeta, total)) return "Pago rechazado: " + Main.euros(total); // no se ha tocado el stock
51 inventario.retirar(producto, cantidad);
52 return "Factura " + facturas.emitir() + ": " + cantidad + " × " + producto + " " + Main.euros(importe)
53 + " + envío " + Main.euros(envio) + " = " + Main.euros(total);
54 }
55
56 String stock(String producto) {
57 return inventario.existe(producto) ? "Stock de " + producto + ": " + inventario.stock(producto) : "Producto desconocido: " + producto;
58 }
59}
60
61public class Main {
62 static String euros(double x) {
63 return String.format(Locale.ROOT, "%.2f", x).replace('.', ',') + " €";
64 }
65
66 public static void main(String[] args) {
67 Tienda tienda = new Tienda();
68 Scanner sc = new Scanner(System.in);
69 while (sc.hasNextLine()) {
70 String linea = sc.nextLine().trim();
71 if (linea.isEmpty()) continue;
72 String[] p = linea.split("\\s+");
73 try {
74 if (p[0].equals("comprar") && p.length == 5) System.out.println(tienda.comprar(p[1], Integer.parseInt(p[2]), p[3], p[4]));
75 else if (p[0].equals("stock") && p.length == 2) System.out.println(tienda.stock(p[1]));
76 else System.out.println("Orden no válida: " + linea);
77 } catch (NumberFormatException e) {
78 System.out.println("Orden no válida: " + linea);
79 }
80 }
81 }
82}El orden de los pasos es una decisión de negocio que queda escrita en un solo sitio, la fachada, en lugar de repetirse en cada pantalla que vende.
En una aplicación real, esta clase sería el servicio de pedidos, y el controlador web solo llamaría a comprar.
Test
Test: Facade
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é hace una fachada?
2.¿Pueden usarse las clases del subsistema sin pasar por la fachada?
3.En una aplicación por capas, ¿qué capa suele hacer de fachada?
4.¿Qué riesgo tiene una fachada?
5.¿En qué se diferencia de un Adapter?