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
- Cas A: el viatge complet d'una comanda — sis patrons, cinc costures
- Les costures, una a una
- Cas B: el mòdul de fidelització, dissenyat des de zero amb el mètode
- Els trade-offs: on vam decidir no usar patró
- 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:
- 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. - 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. - Strategy —
CalculEnviamentcalcula les despeses amb la variant del mercat. El seu paper: un càlcul intercanviable senseifa la façana. - Builder —
Comanda.Buildermunta l'objecte amb les seves línies, enviament i impostos, validant invariants abuild(). El seu paper: construcció complexa fora del constructor. - 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. - Observer —
transitarAdifon 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
forsobre unaList<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
ExpressioReglade 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'
AcumuladorPuntsno 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
forde 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
- Què són els Patrons de Disseny?
- Història i Origen dels Patrons de Disseny
- Principis de Disseny: SOLID i Altres Fonaments
- UML Essencial per Entendre Patrons
- Classificació dels Patrons de Disseny
- Avantatges i Desavantatges d'Usar Patrons de Disseny
Mòdul 2: Patrons Creacionals
- Introducció als Patrons Creacionals
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
- Comparativa i Elecció de Patrons Creacionals
Mòdul 3: Patrons Estructurals
- Introducció als Patrons Estructurals
- Adapter
- Bridge
- Composite
- Decorator
- Facade
- Flyweight
- Proxy
- Comparativa i Elecció de Patrons Estructurals
Mòdul 4: Patrons de Comportament
- Introducció als Patrons de Comportament
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
- Comparativa i Elecció de Patrons de Comportament
Mòdul 5: Aplicació de Patrons de Disseny
- Com Seleccionar el Patró Adequat
- Exemples Pràctics d'Ús de Patrons
- Patrons de Disseny en Projectes Reals
- Refactorització Usant Patrons de Disseny
- Antipatrons: Quan els Patrons es Tornen un Problema
Mòdul 6: Patrons de Disseny Avançats
- Patrons de Disseny en Arquitectures Modernes
- Patrons de Disseny en Microserveis
- Patrons de Disseny en Sistemes Distribuïts
- Patrons de Concurrència
- Patrons de Disseny en Desenvolupament Àgil
