Als mòduls 2 a 4 cada patró va tenir la seva lliçó i el seu racó de PideYa; a la lliçó anterior vas aprendre el mètode per triar-los. Aquesta lliçó ajunta totes dues coses i les porta a l'escala real: funcionalitats completes on diversos patrons col·laboren, i on l'interessant ja no és cada peça sinó les costures — el punt exacte on un patró acaba i un altre comença, i per què la frontera és allà i no en un altre lloc. Veurem dos casos: el flux complet d'una comanda (sis patrons ja construïts, vistos per fi d'una sola passada) i un mòdul nou de fidelització construït des de zero aplicant el mètode — incloses les decisions de no usar patró, que en codi real són la meitat del disseny.

Contingut

  1. Cas A: el viatge complet d'una comanda — sis patrons, cinc costures
  2. Les costures, una a una
  3. Cas B: el mòdul de fidelització, dissenyat des de zero amb el mètode
  4. Els trade-offs: on vam decidir no usar patró
  5. Exercicis i conclusió

Cas A: el viatge complet d'una comanda

Cada patró d'aquesta seqüència té la seva lliçó; el que mai no havíem dibuixat és el conjunt. Un client prem "confirmar comanda" i això és el que passa:

sequenceDiagram
    participant App as App del client
    participant F as FacanaCheckout
    participant CH as Cadena de validacio
    participant S as CalculEnviament (Strategy)
    participant B as Comanda.Builder
    participant P as Comanda (State)
    participant O as Observadors

    App->>F: confirmarComanda(cistella, client)
    F->>CH: gestionar(peticio)
    Note over CH: Frau → Zona → Estoc → Minim<br/>(qualsevol pot tallar)
    CH-->>F: aprovada
    F->>S: calcular(cistella, adreca)
    S-->>F: despesesEnviament
    F->>B: construir amb linies, enviament, impostos
    B-->>F: Comanda (en estat CREADA)
    F->>P: pagar()
    Note over P: EstatCreada autoritza<br/>i transita a PAGADA
    P->>O: transitarA difon el canvi
    Note over O: NotificadorClient, MonitorCuina,<br/>PanellEstadistiques... reaccionen
    F-->>App: ConfirmacioComanda

En text, la cadena de responsabilitats (amb minúscula) és aquesta:

  1. Facade — FacanaCheckout és l'únic punt d'entrada: l'app no coneix ni la cadena, ni les estratègies, ni el builder. El seu paper: orquestrar i ocultar.
  2. Chain of Responsibility — la façana dispara la validació (GestorFrau → GestorZonaRepartiment → GestorEstoc → GestorQuantiaMinima). El seu paper: decidir si la comanda entra, sense que la façana sàpiga quants filtres hi ha.
  3. Strategy — CalculEnviament calcula les despeses amb la variant del mercat. El seu paper: un càlcul intercanviable sense if a la façana.
  4. Builder — Comanda.Builder munta l'objecte amb les seves línies, enviament i impostos, validant invariants a build(). El seu paper: construcció complexa fora del constructor.
  5. State — la comanda neix en CREADA i pagar() transita a PAGADA només si l'estat ho autoritza. El seu paper: que cada operació sigui legal només quan toca.
  6. Observer — transitarA difon la transició a client, cuina i estadístiques. El seu paper: que els interessats se n'assabentin sense que la comanda els conegui. La màxima del mòdul 4 continua vigent: State autoritza, Observer difon.

Les costures, una a una

El que fa que això sigui un disseny i no un munt de patrons és on és cada frontera:

Costura façana–cadena. La façana coneix un objecte: la primera baula (injectada ja muntada, com es va configurar a 04-02). Si demà compliance exigeix un cinquè filtre, s'afegeix al muntatge de la cadena i la façana no canvia. La costura és la interfície GestorComanda: per sobre seu, orquestració; per sota, política d'admissió.

public class FacanaCheckout {

    private final GestorComanda cadenaValidacio;      // primera baula, ja muntada
    private final CalculEnviament calculEnviament;    // estrategia del mercat
    private final RegistreEsdeveniments esdeveniments;

    public ConfirmacioComanda confirmarComanda(Cistella cistella, Client client) {
        // Costura 1: la facana delega l'admissio i nomes mira el resultat
        ResultatValidacio resultat = cadenaValidacio.gestionar(new PeticioComanda(cistella, client));
        if (!resultat.aprovada()) {
            throw new ComandaRebutjadaException(resultat.motiu());
        }

        // Costura 2: el calcul variable es a fora; la facana nomes l'invoca
        BigDecimal enviament = calculEnviament.calcular(cistella, client.adreca());

        // Costura 3: la construccio complexa viu al Builder, no aqui
        Comanda comanda = new Comanda.Builder(client)
                .linies(cistella.linies())
                .despesesEnviament(enviament)
                .build();                 // neix en estat CREADA

        // Costura 4: la facana demana l'operacio; l'ESTAT decideix si es legal
        comanda.pagar();                  // CREADA → PAGADA... i alla salta la costura 5:
                                          // transitarA difon als observadors.
        return new ConfirmacioComanda(comanda);
    }
}

Fixa't en el que la façana no fa: no valida (cadena), no calcula (estratègia), no construeix (builder), no canvia estats a mà (State) i no notifica ningú (Observer). Deu línies d'orquestració pura — cada responsabilitat expulsada cap al patró el símptoma del qual la reclamava. Aquesta és la prova que les costures estan ben posades: la façana continua sent avorrida.

Costura State–Observer — la més fina del sistema i la més fàcil de trencar. Un sol punt les connecta:

// Dins de Comanda: l'UNIC lloc on tots dos patrons es toquen
void transitarA(EstatComanda nouEstat) {
    EstatComanda anterior = this.estat;
    this.estat = nouEstat;                            // State: la transicio ja autoritzada
    observadors.forEach(o -> o.enCanviarEstat(this, anterior, nouEstat)); // Observer
}

Si un observador comencés a provocar transicions (cuina que en assabentar-se de PAGADA crida preparar() sobre la mateixa comanda en el mateix fil), la difusió es tornaria recursiva i l'ordre de notificació passaria a ser lògica de negoci encoberta — el senyal que aquesta reacció pertany a un Mediator o a una ordre explícita, no a l'observador.

Cas B: el mòdul de fidelització, des de zero

Negoci demana: "programa de punts: cada comanda lliurada acumula punts segons regles per nivell (bronze/plata/or); els punts es bescanvien per cupons; els cupons s'apliquen al checkout". No hi ha codi previ. Apliquem el mètode de 05-01 decisió a decisió:

Decisió Forces (què varia / què és estable) Diagnòstic Resultat
Com s'assabenta fidelització dels lliuraments? Varia qui escolta; estable la comanda Difusió a interessat anònim, sense protocol Observer: AcumuladorPunts implements ObservadorComanda — ens subscrivim a la infraestructura existent; la comanda no canvia ni una línia
Com es calculen els punts per nivell? Varia l'algorisme per nivell (or multiplica, plata arrodoneix, bronze pla) Algorismes alternatius triats des de fora Strategy: CalculPunts amb una implementació per nivell
Com s'apliquen els cupons al total? Varia la regla de descompte; estable el checkout Ídem — i les regles ja es combinen (cupó + enviament gratis) Strategy per a la regla + els cupons aplicables com a llista ordenada (vegeu trade-offs)
On s'enganxa al checkout? Estable: la façana orquestra El pas "aplicar descomptes" és una crida més Una línia nova a FacanaCheckout, sense patró nou
Com es creen els cupons en bescanviar? Tres tipus avui, campanya nova cada trimestre El bescanvi sap què vol, no quina classe Factory Method amb registre de Suppliers, rèplica del RegistreNotificadors
El nivell del client (bronze→plata→or)? Puja per punts, baixa per inactivitat Transicions amb regles pròpies per etapa? Sí, però... Espera: avui és només un llindar numèric — enum + CalculPunts per nivell. Nota d'intenció: "si apareixen operacions legals per nivell, avaluar State"

El cor del mòdul, amb les peces triades:

// Observer: fidelitzacio se subscriu; la resta del sistema ni s'assabenta que existeix
public class AcumuladorPunts implements ObservadorComanda {

    private final RepositoriPunts repositori;
    private final Map<Nivell, CalculPunts> calculs;   // Strategy per nivell

    @Override
    public void enCanviarEstat(Comanda comanda, EstatComanda anterior, EstatComanda nou) {
        if (!(nou instanceof EstatLliurada)) {
            return;                                   // nomes ens interessa el lliurament
        }
        Client client = comanda.client();
        CalculPunts calcul = calculs.get(client.nivell());
        int punts = calcul.calcular(comanda);         // l'algorisme del nivell, i nomes ell
        repositori.abonar(client, punts);
    }
}

// Strategy: cada nivell encapsula LA SEVA aritmetica, l'acumulador no sap quina usa
public class CalculPuntsOr implements CalculPunts {
    @Override
    public int calcular(Comanda comanda) {
        return comanda.total().multiply(new BigDecimal("2")).intValue(); // x2 per a or
    }
}

La decisió més valuosa de la taula és la primera: reutilitzar l'Observer existent en comptes d'inventar infraestructura. Un mòdul nou ben dissenyat s'acobla a les costures que el sistema ja ofereix — per això les costures importen més que els patrons.

Els trade-offs: on vam decidir no usar patró

Tres temptacions descartades, amb el seu perquè — perquè el disseny real s'explica també per allò que no es va construir:

  • Chain of Responsibility per aplicar els cupons en ordre? Temptador: "cada cupó decideix si aplica i passa al següent"... però tots els cupons aplicables s'han d'aplicar, ningú no talla la cadena. Falla la prova del cotó de 04-13. Un for sobre una List<ReglaDescompte> ordenada fa el mateix amb zero classes extres:
BigDecimal total = subtotal;
for (ReglaDescompte regla : cuponsAplicables) {     // ordre: mes descompte primer
    total = regla.aplicar(total, cistella);         // Strategy si; Chain no calia
}
  • Interpreter per a les regles de bescanvi? ("500 punts I nivell >= plata"). Ja existeix ExpressioRegla de 04-04 per a promocions... però les regles de bescanvi avui són dues i les escriu el mateix equip en Java, no màrqueting en text. Sense autor extern del minillenguatge, Interpreter és cost sense client. Nota d'intenció al codi i a una altra cosa.
  • Façana pròpia per a fidelització? El mòdul té tres operacions públiques (consultarPunts, bescanviar, aplicarCupons) sobre dues classes. Una façana davant de tan poc és una porta monumental per a un traster — es reavaluarà si el mòdul creix.

El saldo final del mòdul: dos Strategy, un Observer reutilitzat, un Factory Method amb registre — i tres patrons descartats amb argument. Això és aplicar el catàleg amb criteri: cada patró present té un símptoma que el reclama, i cada absència té un perquè articulat.

Errors Comuns i Consells

  • Dissenyar el mòdul "per patrons" ("necessitarà el seu Singleton, la seva Factory i la seva Facade") en comptes de per decisions. La taula decisió a decisió del cas B és l'antídot: cada fila té forces i diagnòstic, no quota de patrons.
  • Duplicar infraestructura en comptes d'endollar-se a les costures existents. Abans de crear el teu propi sistema d'esdeveniments, cadena o fàbrica, mira si el sistema ja n'exposa un (l'AcumuladorPunts no va inventar res: s'hi va subscriure).
  • Costures gruixudes: si la façana comença a validar "només aquest cas especial" o un observador comença a orquestrar, la frontera s'està desfent. La façana ha de continuar sent avorrida; l'observador, reactiu.
  • No registrar els descarts. El proper desenvolupador veurà el for de cupons i pensarà "aquí falta un Chain". Un comentari de dues línies amb el perquè del descart val més que l'elegància silenciosa.
  • Consell: en revisió de disseny, demana sempre les dues llistes — patrons usats i patrons descartats. Un disseny sense descarts gairebé mai no ha estat dissenyat: ha estat decorat.

Exercicis

Exercici 1: mapa de costures

Sense mirar el diagrama: dibuixa (o descriu) el viatge de la comanda del cas A anomenant els sis patrons i les cinc costures (quina interfície o punt de codi separa cada parell). Després respon: si negoci demana "les comandes de més de 100 € requereixen verificació telefònica abans de cuinar-se", quina costura absorbeix el canvi i quins patrons no se n'assabenten?

Exercici 2: estendre fidelització amb el mètode

Negoci amplia: "el dia de l'aniversari del client, les seves comandes acumulen punts dobles, es combinin com es combinin amb el seu nivell". Aplica el mètode: forces, diagnòstic, candidat — i vés amb compte: hi ha una solució amb patró estructural del mòdul 3 i una solució sense patró; presenta totes dues i tria segons els filtres.

Exercici 3: detectar el descart erroni

Un company va implementar l'aplicació de cupons com a Chain of Responsibility real: cada cupó hereta de GestorCupo, aplica el seu descompte i crida seguent.gestionar(...). Funciona. Escriu (a) per què formalment no és un Chain encara que ho sembli, (b) quin problema pràctic causarà el dia que un cupó hagi de "tallar" de debò (cupó exclusiu no combinable), (c) la refactorització mínima cap a la versió amb llista.

Solucions

Solució 1: Costures: (1) app–façana: el mètode confirmarComanda (l'app no veu res més); (2) façana–cadena: la interfície GestorComanda de la primera baula; (3) façana–estratègia: la interfície CalculEnviament; (4) façana–builder: l'API fluida de Comanda.Builder; (5) comanda–observadors: transitarA. El canvi de la verificació telefònica és una regla d'admissió condicional que pot tallar → nova baula GestorVerificacioTelefonica al muntatge de la cadena (costura 2)... o, si la verificació és asíncrona (esperar la trucada), un estat nou EN_VERIFICACIO al cicle de vida (costura 5, State). Amb la lectura síncrona: Builder, Strategy, State i Observer no se n'assabenten; la façana tampoc — només canvia el muntatge de la cadena.

Solució 2: Forces: varia un multiplicador temporal que se suma sobre el càlcul del nivell; estable la interfície CalculPunts i l'acumulador. Opció amb patró: Decorator — CalculPuntsAniversari implements CalculPunts que embolcalla el càlcul del nivell i en duplica el resultat; combina amb qualsevol nivell sense tocar-lo (exactament els extres de 03-05). Opció sense patró: un if (client.esElSeuAniversari()) punts *= 2; a l'acumulador. Filtres: si els "punts dobles" són l'únic modificador previst, l'if guanya (KISS); si màrqueting ja parla de "punts dobles els dimarts" i "x3 la teva primera setmana premium" — modificadors que s'apilen — l'eix de variació és real i Decorator paga el seu cost. La resposta honesta depèn del roadmap, i dir-ho així és la resposta correcta.

Solució 3: (a) La prova del cotó: en un Chain cada baula decideix atendre o passar i pot tallar; aquí tots apliquen sempre i ningú no talla — és una delegació incondicional, o sigui l'estructura de Decorator amb nom de Chain, i la intenció real és "aplicar-los tots" (un recorregut). (b) Quan arribi el cupó exclusiu, tallar la cadena farà que el resultat depengui de l'ordre de muntatge de manera invisible (talla abans o després del cupó d'enviament gratis?), i el bug serà de configuració, indetectable al codi dels cupons. Amb llista explícita, l'exclusivitat és una regla visible (filtrar abans d'iterar). (c) Extreure de cada GestorCupo la seva lògica cap a ReglaDescompte.aplicar(total, cistella), eliminar el camp seguent, i substituir l'arrencada de la cadena pel for sobre la llista ordenada — pas a pas i amb tests, com mana la lliçó de refactorització.

Conclusió

Has vist el catàleg funcionant a escala real: sis patrons col·laborant en el viatge d'una comanda amb la façana com a directora d'una orquestra que no toca cap instrument, i un mòdul nou dissenyat decisió a decisió — amb els seus patrons triats per símptoma, la seva infraestructura reutilitzada per les costures i els seus tres descarts argumentats. La lliçó de fons: un bon disseny es reconeix tant per on són les fronteres com per quins patrons l'habiten. Però PideYa és el nostre laboratori; la pregunta natural és si el programari que uses cada dia — el JDK, Spring, Hibernate — està construït igual. Ho està, i aprendre a reconèixer els seus patrons en llegir-ne el codi és la lliçó següent: Patrons de Disseny en Projectes Reals.

Curs de Patrons de Disseny de Programari

Mòdul 1: Introducció als Patrons de Disseny

Mòdul 2: Patrons Creacionals

Mòdul 3: Patrons Estructurals

Mòdul 4: Patrons de Comportament

Mòdul 5: Aplicació de Patrons de Disseny

Mòdul 6: Patrons de Disseny Avançats

Mòdul 7: Recursos Addicionals i Conclusió

© Copyright 2026. Tots els drets reservats