El mòdul anterior va acabar amb el naixement dels objectes resolt: fàbriques, builders i prototips decideixen què s'instancia, com es munta i d'on es parteix. Però un sistema no és una col·lecció d'objectes ben nascuts: és una xarxa d'objectes connectats. I a PideYa aquesta xarxa ja comença a tensar-se: un SDK de pagaments extern amb una interfície que no encaixa amb la nostra PassarellaPagament, una carta de restaurant que és un arbre de seccions dins de seccions, extres de plats que multipliquen subclasses, un checkout que obliga cada pantalla a conèixer cinc subsistemes... Cap d'aquests problemes no va de crear objectes: van de compondre'ls. Aquesta és la segona família del catàleg GoF, i aquesta lliçó n'és el mapa.
Contingut
- El problema comú: compondre sense acoblar
- Panorama dels set patrons estructurals
- Àmbit de classe i àmbit d'objecte
- Símptomes a PideYa que cal un estructural
- Com llegirem cada patró en aquest mòdul
- Exercicis i conclusió
El problema comú: compondre sense acoblar
Els patrons creacionals responien una pregunta: qui decideix quina classe concreta s'instancia? Els estructurals en responen una altra: com s'assemblen classes i objectes en estructures més grans sense que l'estructura es torni rígida?
La paraula clau és estructura: relacions entre peces. I l'enemic és el de sempre, amb una altra cara: l'acoblament. Al mòdul 2 l'acoblament s'esmunyia pel new; aquí s'esmuny per les connexions:
- Una classe que depèn de la interfície exacta d'una altra que no controlem (una API de tercers): quan el tercer canvia, o quan volem canviar de tercer, tot es trenca.
- Una jerarquia d'herència que creix multiplicativament: cada dimensió de variació nova duplica les subclasses.
- Un client que per fer una operació ha de conèixer i coordinar mitja dotzena d'objectes: el coneixement de l'estructura interna s'escampa per tot el sistema.
- Estructures recursives (arbres part-tot) tractades amb
if (esGrup) ... else ...pertot arreu. - Objectes cars (memòria, xarxa, càrrega) creats i connectats sense control, perquè ningú no fa de mitjancer en l'accés.
Els set patrons estructurals són set maneres d'organitzar les connexions perquè continuïn complint el que vam aprendre a la lliçó de principis: programar contra interfícies, preferir composició sobre herència, i poder estendre sense modificar. De fet, veuràs que gairebé tots els estructurals són aplicacions sistemàtiques de "composició sobre herència": un objecte que embolcalla, conté o remet a d'altres, en lloc d'una jerarquia que ho hereta tot.
Panorama dels set patrons estructurals
La foto completa del mòdul, cada patró en una línia. No intentis memoritzar-la ara: hi tornarem, ampliada, a la comparativa final.
| Patró | Intenció en una línia |
|---|---|
| Adapter | Convertir la interfície d'una classe existent en la interfície que el client espera |
| Bridge | Separar una abstracció de la seva implementació perquè totes dues evolucionin per separat |
| Composite | Compondre objectes en arbres part-tot i tractar fulles i grups de manera uniforme |
| Decorator | Afegir responsabilitats a un objecte dinàmicament, embolcallant-lo, sense herència |
| Facade | Oferir una interfície simple i unificada davant d'un subsistema complex |
| Flyweight | Compartir l'estat comú de moltíssims objectes petits per estalviar memòria |
| Proxy | Posar un substitut amb la mateixa interfície que controla l'accés a l'objecte real |
Una observació que convé fer ja, perquè evita la confusió més gran del mòdul: quatre dels set (Adapter, Decorator, Facade i Proxy) comparteixen mecànica —un objecte davant d'un altre, delegant— i es distingeixen només per la intenció. Adapter canvia la interfície; Decorator la conserva i hi afegeix responsabilitats; Facade la simplifica; Proxy la conserva i en controla l'accés. Recorda la definició de patró de la primera lliçó: context, problema, solució i intenció. En aquesta família, la intenció és sovint l'única cosa que separa un patró d'un altre; els dedicarem un acarament complet a la comparativa.
Els altres tres organitzen multituds: Composite organitza objectes en arbres, Bridge organitza jerarquies en dos eixos independents, i Flyweight organitza milers d'instàncies compartint allò comú.
Àmbit de classe i àmbit d'objecte
A la classificació GoF vam veure que cada patró té un àmbit: de classe si la relació es fixa amb herència en temps de compilació, o d'objecte si s'estableix amb composició en temps d'execució.
A la família estructural el recompte és rotund: sis dels set són d'àmbit d'objecte. Només Adapter existeix en les dues variants, i per això és l'exemple perfecte per entendre la diferència:
- Adapter de classe: l'adaptador hereta de la classe que adapta i alhora implementa la interfície esperada. La relació queda soldada en compilar: aquell adaptador serveix per a aquella classe concreta i les seves subclasses, i prou.
- Adapter d'objecte: l'adaptador conté una referència a l'objecte adaptat i hi delega. La relació es decideix en construir: el mateix adaptador pot embolcallar qualsevol objecte compatible, fins i tot decidit en execució.
classDiagram
class InterficieEsperada { <<interface>> +operacio() }
class ClasseExistent { +operacioVella() }
class AdaptadorDeClasse { +operacio() }
class AdaptadorDObjecte { -adaptat: ClasseExistent +operacio() }
InterficieEsperada <|.. AdaptadorDeClasse
ClasseExistent <|-- AdaptadorDeClasse : hereta (àmbit de classe)
InterficieEsperada <|.. AdaptadorDObjecte
AdaptadorDObjecte o-- ClasseExistent : conté (àmbit d'objecte)
Que gairebé tota la família sigui d'àmbit d'objecte no és casualitat: és el principi de composició sobre herència fet catàleg. L'herència fixa l'estructura per sempre; la composició permet reconfigurar-la —embolcallar, desembolcallar, recombinar— mentre el programa corre. Veuràs aquesta idea repetida a cada lliçó: el decorador s'apila en execució, l'arbre del composite es munta en execució, el pont es creua en execució.
Símptomes a PideYa que cal un estructural
Igual que el mòdul 2 va començar catalogant els dolors del new, aquest comença catalogant els dolors de la composició. Tots són reals a PideYa avui; cadascun té la seva lliçó:
| Símptoma a PideYa | Olor de disseny | Patró que el tracta |
|---|---|---|
Volem cobrar amb un SDK de PayPal heretat, però la seva interfície (makePayment(long, String)) no s'assembla gens a la nostra PassarellaPagament del mòdul 2 |
Interfícies incompatibles entre el nostre codi i codi que no podem tocar | Adapter |
| Notificacions de confirmació, retard i promoció × canals push, SMS i email: 3×3 = 9 subclasses, i cada tipus o canal nou multiplica | Jerarquia que creix per producte de dues dimensions independents | Bridge |
La carta d'un restaurant té seccions, subseccions i plats; el codi és ple de if (esSeccio) ... else ... |
Estructura arbre part-tot tractada amb condicionals en lloc de recursió uniforme | Composite |
"Doble formatge", "sense gluten", "ració gran": cada combinació d'extres sobre un plat demana la seva subclasse (PizzaAmbFormatgeSenseGluten...) |
Explosió combinatòria de subclasses per afegir responsabilitats | Decorator |
| Per confirmar una comanda, l'app mòbil crida validació, impostos, passarel·la, persistència i notificacions, en l'ordre correcte, amb la gestió d'errors correcta | Clients obligats a conèixer i orquestrar un subsistema sencer | Facade |
| El mapa en temps real mostra milers de marcadors de repartidors i restaurants, i cadascun carrega la seva pròpia icona: l'app consumeix memòria com si no hi hagués demà | Milers d'objectes que dupliquen el mateix estat immutable | Flyweight |
| Les fotos dels plats pesen diversos MB i es carreguen totes en obrir la carta; i qualsevol empleat pot invocar operacions que haurien de ser només d'administradors | Accés a l'objecte real sense control: sense càrrega mandrosa, sense permisos | Proxy |
Fixa't en el criteri, que és el mateix de tot el curs: primer el símptoma, després el patró. Cap de les lliçons següents no comença per "apliquem X"; totes comencen per un dolor concret de PideYa i arriben al patró com a resposta proporcionada. Si al teu projecte no reconeixes el símptoma, la lliçó de la balança ja et va dir què fer: res.
Com llegirem cada patró en aquest mòdul
Les set lliçons de patró segueixen el mateix esquelet que les del mòdul 2, perquè puguis comparar-los entre si sense esforç:
- El problema a PideYa: el símptoma de la taula anterior, amb codi del primer intent (el dolent).
- Estructura del patró: intenció GoF i
classDiagrammermaid amb els rols del patró, tal com vam aprendre a la lliçó d'UML. - Implementació Java completa, explicada pas a pas.
- Variants rellevants (de classe/objecte, transparent/segura, estàtica/dinàmica segons el patró).
- Quan usar-lo i quan no, amb l'honestedat de costos habitual.
- Relació amb altres patrons: només mencions amb enllaç; cada cosa es desenvolupa a la seva lliçó.
- Errors comuns, exercicis amb solució i conclusió que enllaça amb la lliçó següent.
I continuem construint sobre el que ja tenim: la PassarellaPagament i la família per mercat del mòdul 2 reapareixeran a Adapter i Facade; els Notificador del Factory Method seran decorats i pontats; el Comanda.Builder participarà en el checkout de la façana. Els patrons no viuen en lliçons estanques: viuen en el mateix sistema.
Exercicis
Exercici 1: classificar símptomes
Per a cada situació nova de PideYa, digues quin patró estructural de la taula sembla que hi apunta (no cal que sàpigues encara com funciona; n'hi ha prou de casar símptoma i intenció d'una línia):
- L'equip de dades vol consultar comandes amb una llibreria d'informes la interfície de lectura de la qual no coincideix amb el nostre repositori de comandes.
- Un "menú del dia" conté primer, segon i postres; i un "menú familiar" conté dos menús del dia i una beguda gran. Volem calcular el preu de qualsevol cosa que es pugui demanar, sigui plat solt o menú niat.
- Volem que certes crides al servei de repartiment quedin registrades a
RegistreEsdevenimentsi es reintentin si fallen, sense tocar la classe del servei. - La pantalla "estat de la meva comanda" necessita dades de cuina, de repartiment i de pagaments; avui l'app crida els tres serveis i combina els resultats a mà.
Exercici 2: classe o objecte?
Explica amb les teves paraules per què un Adapter d'objecte pot adaptar en execució "qualsevol passarel·la vella que ens arribi" i un Adapter de classe no. Quina relació UML de la lliçó 01-04 usa cadascun?
Solucions
Solució 1:
- Adapter: dues interfícies que no encaixen i una d'elles (la llibreria) no és nostra.
- Composite: estructura part-tot recursiva (menús que contenen menús) amb una operació uniforme (preu).
- Decorator (amb un matís de Proxy que afinarem a les seves lliçons): afegir responsabilitats —logging, reintents— embolcallant, sense modificar la classe.
- Facade: un punt únic i simple que orquestra diversos subsistemes per a un cas d'ús.
Solució 2: l'Adapter d'objecte conté (composició/agregació, la relació o--) una referència tipada a allò adaptat, que se li passa en construir: en execució pot rebre una instància o una altra, fins i tot de subclasses diferents. L'Adapter de classe hereta (generalització, <|--) d'una classe concreta: la relació queda fixada en compilar i només val per a aquella classe. És exactament la diferència entre àmbit d'objecte i àmbit de classe, i la raó que "composició sobre herència" sigui el lema d'aquesta família.
Conclusió
Ja tens el mapa del mòdul: set patrons que resolen un mateix problema de fons —compondre classes i objectes en estructures més grans mantenint-les flexibles— atacant-lo per set fronts: interfícies que no encaixen, jerarquies que es multipliquen, arbres part-tot, responsabilitats afegides, subsistemes complexos, multituds d'objectes i accessos per controlar. Saps que gairebé tots són d'àmbit d'objecte (composició sobre herència portat a catàleg), i que quatre d'ells comparteixen mecànica i es distingeixen per la intenció, així que la intenció serà la nostra brúixola.
Comencem pel més immediat de tots, el que apareix sempre que el món exterior truca a la nostra porta amb una interfície que no és la nostra: PideYa necessita cobrar amb un SDK heretat que no parla PassarellaPagament, i no podem tocar ni l'SDK ni tot el codi que ja usa PassarellaPagament. La peça no encaixa... llevat que li construïm un endoll. Ens veiem a Adapter.
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
