En los módulos 2 a 4 cada patrón tuvo su lección y su rincón de PideYa; en la lección anterior aprendiste el método para elegirlos. Esta lección junta ambas cosas y las lleva a la escala real: funcionalidades completas donde varios patrones colaboran, y donde lo interesante ya no es cada pieza sino las costuras — el punto exacto donde un patrón termina y otro empieza, y por qué la frontera está ahí y no en otro sitio. Veremos dos casos: el flujo completo de un pedido (seis patrones ya construidos, vistos por fin de una sola pasada) y un módulo nuevo de fidelización construido desde cero aplicando el método — incluidas las decisiones de no usar patrón, que en código real son la mitad del diseño.

Contenido

  1. Caso A: el viaje completo de un pedido — seis patrones, cinco costuras
  2. Las costuras, una a una
  3. Caso B: el módulo de fidelización, diseñado desde cero con el método
  4. Los trade-offs: dónde decidimos no usar patrón
  5. Ejercicios y conclusión

Caso A: el viaje completo de un pedido

Cada patrón de esta secuencia tiene su lección; lo que nunca habíamos dibujado es el conjunto. Un cliente pulsa "confirmar pedido" y esto es lo que ocurre:

sequenceDiagram
    participant App as App del cliente
    participant F as FachadaCheckout
    participant CH as Cadena de validación
    participant S as CalculoEnvio (Strategy)
    participant B as Pedido.Builder
    participant P as Pedido (State)
    participant O as Observadores

    App->>F: confirmarPedido(carrito, cliente)
    F->>CH: manejar(solicitud)
    Note over CH: Fraude → Zona → Stock → Mínimo<br/>(cualquiera puede cortar)
    CH-->>F: aprobada
    F->>S: calcular(carrito, direccion)
    S-->>F: gastosEnvio
    F->>B: construir con líneas, envío, impuestos
    B-->>F: Pedido (en estado CREADO)
    F->>P: pagar()
    Note over P: EstadoCreado autoriza<br/>y transita a PAGADO
    P->>O: transitarA difunde el cambio
    Note over O: NotificadorCliente, MonitorCocina,<br/>PanelEstadisticas... reaccionan
    F-->>App: ConfirmacionPedido

En texto, la cadena de responsabilidades (con minúscula) es esta:

  1. FacadeFachadaCheckout es el único punto de entrada: la app no conoce ni la cadena, ni las estrategias, ni el builder. Su papel: orquestar y ocultar.
  2. Chain of Responsibility — la fachada dispara la validación (ManejadorFraudeManejadorZonaRepartoManejadorStockManejadorImporteMinimo). Su papel: decidir si el pedido entra, sin que la fachada sepa cuántos filtros hay.
  3. StrategyCalculoEnvio calcula los gastos con la variante del mercado. Su papel: un cálculo intercambiable sin if en la fachada.
  4. BuilderPedido.Builder monta el objeto con sus líneas, envío e impuestos, validando invariantes en build(). Su papel: construcción compleja fuera del constructor.
  5. State — el pedido nace en CREADO y pagar() transita a PAGADO solo si el estado lo autoriza. Su papel: que cada operación sea legal solo cuando toca.
  6. ObservertransitarA difunde la transición a cliente, cocina y estadísticas. Su papel: que los interesados se enteren sin que el pedido los conozca. La máxima del módulo 4 sigue vigente: State autoriza, Observer difunde.

Las costuras, una a una

Lo que hace que esto sea un diseño y no un montón de patrones es dónde está cada frontera:

Costura fachada–cadena. La fachada conoce un objeto: el primer eslabón (inyectado ya montado, como se configuró en 04-02). Si mañana compliance exige un quinto filtro, se añade al montaje de la cadena y la fachada no cambia. La costura es la interfaz ManejadorPedido: por encima de ella, orquestación; por debajo, política de admisión.

public class FachadaCheckout {

    private final ManejadorPedido cadenaValidacion; // primer eslabón, ya enlazado
    private final CalculoEnvio calculoEnvio;        // estrategia del mercado
    private final RegistroEventos eventos;

    public ConfirmacionPedido confirmarPedido(Carrito carrito, Cliente cliente) {
        // Costura 1: la fachada delega la admisión y solo mira el resultado
        ResultadoValidacion resultado = cadenaValidacion.manejar(new SolicitudPedido(carrito, cliente));
        if (!resultado.aprobada()) {
            throw new PedidoRechazadoException(resultado.motivo());
        }

        // Costura 2: el cálculo variable está fuera; la fachada solo lo invoca
        BigDecimal envio = calculoEnvio.calcular(carrito, cliente.direccion());

        // Costura 3: la construcción compleja vive en el Builder, no aquí
        Pedido pedido = new Pedido.Builder(cliente)
                .lineas(carrito.lineas())
                .gastosEnvio(envio)
                .build();                 // nace en estado CREADO

        // Costura 4: la fachada pide la operación; el ESTADO decide si es legal
        pedido.pagar();                   // CREADO → PAGADO... y ahí salta la costura 5:
                                          // transitarA difunde a los observadores.
        return new ConfirmacionPedido(pedido);
    }
}

Fíjate en lo que la fachada no hace: no valida (cadena), no calcula (estrategia), no construye (builder), no cambia estados a mano (State) y no notifica a nadie (Observer). Diez líneas de orquestación pura — cada responsabilidad expulsada hacia el patrón cuyo síntoma la reclamaba. Esa es la prueba de que las costuras están bien puestas: la fachada sigue siendo aburrida.

Costura State–Observer — la más fina del sistema y la más fácil de romper. Un solo punto las conecta:

// Dentro de Pedido: el ÚNICO lugar donde ambos patrones se tocan
void transitarA(EstadoPedido nuevoEstado) {
    EstadoPedido anterior = this.estado;
    this.estado = nuevoEstado;                        // State: la transición ya autorizada
    observadores.forEach(o -> o.alCambiarEstado(this, anterior, nuevoEstado)); // Observer
}

Si un observador empezara a provocar transiciones (cocina que al enterarse de PAGADO llama a prepararse() sobre el mismo pedido en el mismo hilo), la difusión se volvería recursiva y el orden de notificación pasaría a ser lógica de negocio encubierta — la señal de que esa reacción pertenece a un Mediator o a un comando explícito, no al observador.

Caso B: el módulo de fidelización, desde cero

Negocio pide: "programa de puntos: cada pedido entregado acumula puntos según reglas por nivel (bronce/plata/oro); los puntos se canjean por cupones; los cupones se aplican en el checkout". No hay código previo. Apliquemos el método de 05-01 decisión a decisión:

Decisión Fuerzas (qué varía / qué es estable) Diagnóstico Resultado
¿Cómo se entera fidelización de las entregas? Varía quién escucha; estable el pedido Difusión a interesado anónimo, sin protocolo Observer: AcumuladorPuntos implements ObservadorPedido — nos suscribimos a la infraestructura existente; el pedido no cambia ni una línea
¿Cómo se calculan los puntos por nivel? Varía el algoritmo por nivel (oro multiplica, plata redondea, bronce plano) Algoritmos alternativos elegidos desde fuera Strategy: CalculoPuntos con una implementación por nivel
¿Cómo se aplican los cupones al total? Varía la regla de descuento; estable el checkout Ídem — y las reglas ya combinan (cupón + envío gratis) Strategy para la regla + los cupones aplicables como lista ordenada (ver trade-offs)
¿Dónde se engancha al checkout? Estable: la fachada orquesta El paso "aplicar descuentos" es una llamada más Una línea nueva en FachadaCheckout, sin patrón nuevo
¿Cómo se crean los cupones al canjear? Tres tipos hoy, campaña nueva cada trimestre El canje sabe qué quiere, no cuál clase Factory Method con registro de Suppliers, réplica del RegistroNotificadores
¿El nivel del cliente (bronce→plata→oro)? Sube por puntos, baja por inactividad ¿Transiciones con reglas propias por etapa? Sí, pero... Espera: hoy es solo un umbral numérico — enum + CalculoPuntos por nivel. Nota de intención: "si aparecen operaciones legales por nivel, evaluar State"

El corazón del módulo, con las piezas elegidas:

// Observer: fidelización se suscribe; el resto del sistema ni se entera de que existe
public class AcumuladorPuntos implements ObservadorPedido {

    private final RepositorioPuntos repositorio;
    private final Map<Nivel, CalculoPuntos> calculos; // Strategy por nivel

    @Override
    public void alCambiarEstado(Pedido pedido, EstadoPedido anterior, EstadoPedido nuevo) {
        if (!(nuevo instanceof EstadoEntregado)) {
            return;                                   // solo nos interesa la entrega
        }
        Cliente cliente = pedido.cliente();
        CalculoPuntos calculo = calculos.get(cliente.nivel());
        int puntos = calculo.calcular(pedido);        // el algoritmo del nivel, y solo él
        repositorio.abonar(cliente, puntos);
    }
}

// Strategy: cada nivel encapsula SU aritmética, el acumulador no sabe cuál usa
public class CalculoPuntosOro implements CalculoPuntos {
    @Override
    public int calcular(Pedido pedido) {
        return pedido.total().multiply(new BigDecimal("2")).intValue(); // x2 para oro
    }
}

La decisión más valiosa de la tabla es la primera: reutilizar el Observer existente en vez de inventar infraestructura. Un módulo nuevo bien diseñado se acopla a las costuras que el sistema ya ofrece — por eso las costuras importan más que los patrones.

Los trade-offs: dónde decidimos no usar patrón

Tres tentaciones descartadas, con su porqué — porque el diseño real se cuenta también por lo que no se construyó:

  • ¿Chain of Responsibility para aplicar los cupones en orden? Tentador: "cada cupón decide si aplica y pasa al siguiente"... pero todos los cupones aplicables deben aplicarse, nadie corta la cadena. Falla la prueba del algodón de 04-13. Un for sobre una List<ReglaDescuento> ordenada hace lo mismo con cero clases extra:
BigDecimal total = subtotal;
for (ReglaDescuento regla : cuponesAplicables) {   // orden: mayor descuento primero
    total = regla.aplicar(total, carrito);          // Strategy sí; Chain no hacía falta
}
  • ¿Interpreter para las reglas de canje? ("500 puntos Y nivel >= plata"). Ya existe ExpresionRegla de 04-04 para promociones... pero las reglas de canje hoy son dos y las escribe el propio equipo en Java, no marketing en texto. Sin autor externo del mini-lenguaje, Interpreter es coste sin cliente. Nota de intención en el código y a otra cosa.
  • ¿Facade propia para fidelización? El módulo tiene tres operaciones públicas (consultarPuntos, canjear, aplicarCupones) sobre dos clases. Una fachada delante de tan poco es una puerta monumental para un trastero — se reevaluará si el módulo crece.

El saldo final del módulo: dos Strategy, un Observer reutilizado, un Factory Method con registro — y tres patrones descartados con argumento. Eso es aplicar el catálogo con criterio: cada patrón presente tiene un síntoma que lo reclama, y cada ausencia tiene un porqué articulado.

Errores Comunes y Consejos

  • Diseñar el módulo "por patrones" ("necesitará su Singleton, su Factory y su Facade") en vez de por decisiones. La tabla decisión-a-decisión del caso B es el antídoto: cada fila tiene fuerzas y diagnóstico, no cuota de patrones.
  • Duplicar infraestructura en vez de enchufarse a las costuras existentes. Antes de crear tu propio sistema de eventos, cadena o fábrica, mira si el sistema ya expone uno (el AcumuladorPuntos no inventó nada: se suscribió).
  • Costuras gordas: si la fachada empieza a validar "solo este caso especial" o un observador empieza a orquestar, la frontera se está deshaciendo. La fachada debe seguir siendo aburrida; el observador, reactivo.
  • No registrar los descartes. El próximo desarrollador verá el for de cupones y pensará "aquí falta un Chain". Un comentario de dos líneas con el porqué del descarte vale más que la elegancia silenciosa.
  • Consejo: en revisión de diseño, pide siempre las dos listas — patrones usados y patrones descartados. Un diseño sin descartes casi nunca ha sido diseñado: ha sido decorado.

Ejercicios

Ejercicio 1: mapa de costuras

Sin mirar el diagrama: dibuja (o describe) el viaje del pedido del caso A nombrando los seis patrones y las cinco costuras (qué interfaz o punto de código separa cada par). Después responde: si negocio pide "los pedidos de más de 100 € requieren verificación telefónica antes de cocinarse", ¿qué costura absorbe el cambio y qué patrones no se enteran?

Ejercicio 2: extender fidelización con el método

Negocio amplía: "en el cumpleaños del cliente, sus pedidos acumulan puntos dobles, se combinen como se combinen con su nivel". Aplica el método: fuerzas, diagnóstico, candidato — y cuidado: hay una solución con patrón estructural del módulo 3 y una solución sin patrón; presenta ambas y elige según los filtros.

Ejercicio 3: detectar el descarte erróneo

Un compañero implementó la aplicación de cupones como Chain of Responsibility real: cada cupón hereda de ManejadorCupon, aplica su descuento y llama a siguiente.manejar(...). Funciona. Escribe (a) por qué formalmente no es un Chain aunque lo parezca, (b) qué problema práctico causará el día que un cupón deba "cortar" de verdad (cupón exclusivo no combinable), (c) la refactorización mínima hacia la versión con lista.

Soluciones

Solución 1: Costuras: (1) app–fachada: el método confirmarPedido (la app no ve nada más); (2) fachada–cadena: la interfaz ManejadorPedido del primer eslabón; (3) fachada–estrategia: la interfaz CalculoEnvio; (4) fachada–builder: la API fluida de Pedido.Builder; (5) pedido–observadores: transitarA. El cambio de la verificación telefónica es una regla de admisión condicional que puede cortar → nuevo eslabón ManejadorVerificacionTelefonica en el montaje de la cadena (costura 2)... o, si la verificación es asíncrona (esperar la llamada), un estado nuevo EN_VERIFICACION en el ciclo de vida (costura 5, State). Con la lectura síncrona: Builder, Strategy, State y Observer no se enteran; la fachada tampoco — solo cambia el montaje de la cadena.

Solución 2: Fuerzas: varía un multiplicador temporal que se suma sobre el cálculo del nivel; estable la interfaz CalculoPuntos y el acumulador. Opción con patrón: DecoratorCalculoPuntosCumpleanos implements CalculoPuntos que envuelve al cálculo del nivel y duplica su resultado; combina con cualquier nivel sin tocarlo (exactamente los extras de 03-05). Opción sin patrón: un if (cliente.esSuCumpleanos()) puntos *= 2; en el acumulador. Filtros: si el "puntos dobles" es el único modificador previsto, el if gana (KISS); si marketing ya habla de "puntos dobles los martes" y "x3 en tu primera semana premium" — modificadores que se apilan — el eje de variación es real y Decorator paga su coste. La respuesta honesta depende del roadmap, y decirlo así es la respuesta correcta.

Solución 3: (a) La prueba del algodón: en un Chain cada eslabón decide atender o pasar y puede cortar; aquí todos aplican siempre y nadie corta — es una delegación incondicional, o sea la estructura de Decorator con nombre de Chain, y la intención real es "aplicar todos" (un recorrido). (b) Cuando llegue el cupón exclusivo, cortar la cadena hará que el resultado dependa del orden de montaje de forma invisible (¿corta antes o después del cupón de envío gratis?), y el bug será de configuración, indetectable en el código de los cupones. Con lista explícita, la exclusividad es una regla visible (filtrar antes de iterar). (c) Extraer de cada ManejadorCupon su lógica a ReglaDescuento.aplicar(total, carrito), eliminar el campo siguiente, y sustituir el arranque de la cadena por el for sobre la lista ordenada — paso a paso y con tests, como manda la lección de refactorización.

Conclusión

Has visto el catálogo funcionando a escala real: seis patrones colaborando en el viaje de un pedido con la fachada como director de una orquesta que no toca ningún instrumento, y un módulo nuevo diseñado decisión a decisión — con sus patrones elegidos por síntoma, su infraestructura reutilizada por las costuras y sus tres descartes argumentados. La lección de fondo: un buen diseño se reconoce tanto por dónde están las fronteras como por qué patrones lo habitan. Pero PideYa es nuestro laboratorio; la pregunta natural es si el software que usas cada día — el JDK, Spring, Hibernate — está construido igual. Lo está, y aprender a reconocer sus patrones al leer su código es la siguiente lección: Patrones de Diseño en Proyectos Reales.

Curso de Patrones de Diseño de Software

Módulo 1: Introducción a los Patrones de Diseño

Módulo 2: Patrones Creacionales

Módulo 3: Patrones Estructurales

Módulo 4: Patrones de Comportamiento

Módulo 5: Aplicación de Patrones de Diseño

Módulo 6: Patrones de Diseño Avanzados

Módulo 7: Recursos Adicionales y Conclusión

© Copyright 2026. Todos los derechos reservados