Els patrons vistos fins ara afinen relacions entre poques peces: dues interfícies que no encaixen, dues jerarquies, un arbre, una ceba de capes. Facade opera a una altra escala: quan un subsistema sencer —mitja dotzena de serveis amb el seu ordre, les seves regles i els seus errors— ha de ser usat per molts clients, algú ha de saber orquestrar-lo. La pregunta de disseny és: aquest coneixement viu repetit a cada client, o viu una sola vegada darrere d'una porta simple? A PideYa la resposta la demana a crits el checkout: confirmar una comanda exigeix avui que cada aplicació client coordini cinc subsistemes a mà.
Contingut
- El problema a PideYa: el checkout escampat
- Intenció i estructura del patró
- Implementació Java:
FacanaCheckout - Què NO és una façana
- Façanes per capes i en APIs públiques
- Quan usar-lo i quan no
- Errors comuns, exercicis i conclusió
El problema a PideYa: el checkout escampat
Confirmar una comanda a PideYa involucra, en ordre, amb gestió d'errors a cada pas:
- Validar la cistella: plats disponibles (la carta Composite sap respondre), quantia mínima del restaurant, adreça dins de la zona de repartiment.
- Calcular impostos: amb la
CalculadoraImpostosdel mercat correcte — la família per país que vam blindar amb Abstract Factory. - Cobrar: amb la
PassarellaPagamentdel mercat (Redsys, Conekta o l'AdaptadorPayPal); si el pagament falla, no ha de quedar rastre de la comanda. - Crear i persistir la comanda: muntant-la amb el
Comanda.Builderi desant-la al repositori. - Notificar: al client (confirmació) i al restaurant (nova comanda), via
ServeiNotificacions.
Aquest coneixement —què cridar, en quin ordre, què fer si el pas 3 falla després del 2— avui està copiat a cada client: l'app iOS, l'app Android, la web, el lloc d'atenció telefònica. Les conseqüències són les esperables:
- Duplicació amb divergència: la web valida la quantia mínima abans dels impostos; Android, després. Un arranjament al flux cal repetir-lo per quadruplicat, i alguna còpia sempre queda enrere.
- Acoblament massiu: quatre clients coneixen cinc subsistemes cadascun (20 dependències); canviar la firma d'un servei intern sacseja totes les aplicacions.
- Coneixement a la capa equivocada: la regla "si el cobrament falla, alliberar la cistella i no notificar" és negoci pur, i viu en codi d'interfície d'usuari.
El primer instint podria ser "simplifiquem els serveis". Però els serveis estan bé: cadascun fa una cosa (SRP) i té la seva complexitat legítima. El que sobra no és complexitat de les peces: és coneixement de la coordinació escampat pels clients.
Intenció i estructura del patró
Intenció (GoF): proporcionar una interfície unificada a un conjunt d'interfícies d'un subsistema. Facade defineix una interfície de més alt nivell que fa el subsistema més fàcil d'usar.
La solució és gairebé anticlimàtica de pur simple: una classe que ofereix les operacions d'alt nivell que els clients de debò necessiten ("confirmar aquesta cistella amb aquest mètode de pagament") i que per dins coneix i coordina el subsistema. Tota la sofisticació del patró és a decidir bé aquesta interfície d'alt nivell, no en la seva mecànica.
classDiagram
class FacanaCheckout {
+confirmarComanda(cistella: Cistella, dadesPagament: DadesPagament) ConfirmacioComanda
}
class ValidadorCistella { +validar(cistella) }
class CalculadoraImpostos { <<interface>> +calcular(base) BigDecimal }
class PassarellaPagament { <<interface>> +cobrar(quantia, dades) ResultatPagament }
class RepositoriComandes { +desar(comanda) }
class ServeiNotificacions { +notificarConfirmacio(comanda) }
class AppMobil
class WebPideYa
class AtencioTelefonica
AppMobil --> FacanaCheckout
WebPideYa --> FacanaCheckout
AtencioTelefonica --> FacanaCheckout
FacanaCheckout --> ValidadorCistella
FacanaCheckout --> CalculadoraImpostos
FacanaCheckout --> PassarellaPagament
FacanaCheckout --> RepositoriComandes
FacanaCheckout --> ServeiNotificacions
| Rol GoF | A PideYa |
|---|---|
| Facade | FacanaCheckout |
| Subsystem classes (no coneixen la façana) | ValidadorCistella, CalculadoraImpostos, PassarellaPagament, RepositoriComandes, ServeiNotificacions |
| Client | Apps mòbils, web, atenció telefònica |
La geometria explica la història: abans, un graf de 4 clients × 5 serveis; després, un embut. Els clients depenen d'una classe; el subsistema no sap que la façana existeix (fletxes només d'anada). El recompte de dependències cau de 20 a 4 + 5, i sobretot, el flux de negoci passa a estar escrit una vegada.
Implementació Java: FacanaCheckout
Al mòdul 2 va aparèixer un embrió d'aquesta idea: aquell ServeiCheckout que cobrava amb la família del mercat. La façana el completa amb el flux sencer, i rep les seves peces injectades —les dependents del mercat, acabades de sortir de la FabricaMercat—:
public class FacanaCheckout {
private final ValidadorCistella validador;
private final CalculadoraImpostos impostos;
private final PassarellaPagament passarella;
private final RepositoriComandes repositori;
private final ServeiNotificacions notificacions;
public FacanaCheckout(ValidadorCistella validador,
FabricaMercat fabricaMercat,
RepositoriComandes repositori,
ServeiNotificacions notificacions) {
this.validador = validador;
this.impostos = fabricaMercat.crearCalculadoraImpostos(); // familia coherent
this.passarella = fabricaMercat.crearPassarellaPagament(); // del modul 2
this.repositori = repositori;
this.notificacions = notificacions;
}
/** L'operacio d'alt nivell: tot el flux de confirmacio, una sola crida. */
public ConfirmacioComanda confirmarComanda(Cistella cistella, DadesPagament dadesPagament) {
// 1. Validar: disponibilitat, quantia minima, zona de repartiment.
validador.validar(cistella); // llanca CistellaInvalidaException
// 2. Impostos del mercat correcte.
BigDecimal base = cistella.getTotalSenseImpostos();
BigDecimal impost = impostos.calcular(base);
BigDecimal total = base.add(impost);
// 3. Cobrar. Si falla, aqui s'acaba tot: encara no hi ha comanda per desfer.
ResultatPagament resultat = passarella.cobrar(total, dadesPagament);
if (!resultat.esAcceptat()) {
throw new PagamentRebutjatException(resultat.getMotiu());
}
// 4. Crear la comanda (Builder del modul 2) i persistir-la.
Comanda comanda = Comanda.builder(cistella.getClient(), cistella.getRestaurant())
.linies(cistella.getLinies())
.quantiaTotal(total)
.referenciaPagament(resultat.getIdTransaccio())
.build();
repositori.desar(comanda);
// 5. Notificar a client i restaurant.
notificacions.notificarConfirmacio(comanda);
return new ConfirmacioComanda(comanda.getNumero(), total, comanda.getHoraEstimada());
}
}I els quatre clients queden reduïts a la seva feina — interfície d'usuari:
// A l'app mobil, la web o on sigui:
ConfirmacioComanda conf = facanaCheckout.confirmarComanda(cistella, dadesPagament);
mostrarPantallaExit(conf);Detalls que fan bona aquesta façana:
- L'ordre amb intenció: cobrar abans de persistir fa innecessari el "desfer comanda" si el pagament falla. La decisió més delicada del flux està presa una vegada, al lloc correcte, i comentada.
- Parla l'idioma del client: rep
CistellaiDadesPagament, retorna unaConfirmacioComandacompacta (número, total, hora estimada) — no exposaResultatPagament, ni laComandasencera, ni tipus interns del subsistema. Una façana que filtra tipus interns cap enfora és un túnel, no una porta. - Compon el que ja tenim construït: la coherència per mercat la continua garantint la
FabricaMercat; el muntatge de la comanda, el seu Builder; l'enviament, el servei de notificacions (amb els seus decoradors de reintents, invisibles des d'aquí). La façana no reimplementa res: coordina.
Què NO és una façana
El patró es malinterpreta sovint per excés. Tres delimitacions importants:
No prohibeix l'accés directe al subsistema. El GoF és explícit: els clients que necessitin la potència completa poden continuar usant les classes del subsistema. El panell d'administració de PideYa consulta RepositoriComandes directament per als seus llistats, i és correcte: la façana ofereix una drecera per als casos comuns, no un peatge obligatori. (Una altra cosa és que un equip decideixi a més fer-la punt d'entrada únic d'un mòdul — decisió d'arquitectura legítima, però addicional al patró.)
No afegeix estat ni lògica de negoci pròpia. La façana coordina; les regles viuen al subsistema. Si FacanaCheckout comencés a calcular descomptes o a desar comptadors de comandes, estaria deixant de ser façana per convertir-se en un servei més — amb l'agreujant que la seva posició central la converteix en imant de responsabilitats: el camí directe cap al God Object que catalogarem entre els antipatrons.
No és "qualsevol classe que crida altres". La marca del patró és l'asimetria de complexitat: interfície d'alt nivell simple al davant, subsistema complex al darrere, i clients que gràcies a ella no coneixen el subsistema. Si la "façana" té un mètode per cada mètode intern, és un passamans que afegeix indirecció sense restar complexitat.
Façanes per capes i en APIs públiques
La idea escala més enllà d'una classe solta:
- Façanes per capes: en arquitectures en capes, cada subsistema gran exposa la seva façana i amaga les seves tripes:
FacanaCuina(recepció i estats de comandes),FacanaRepartiment(assignació de repartidors, seguiment),FacanaCheckout. Les capes superiors parlen només amb façanes; els equips poden reorganitzar l'interior del seu subsistema sense trencar ningú. És el patró fent de frontera de mòdul — embrió del que, a una altra escala, faran els serveis a les arquitectures modernes (només menció). - Façanes en APIs públiques: quan PideYa publiqui la seva API per a partners ("crear comanda", "consultar estat"), aquella API serà una façana institucional: operacions d'alt nivell, tipus propis de la frontera, i ni rastre dels vint serveis interns. Les llibreries ho fan igual:
SLF4J("Simple Logging Facade for Java") és una façana confessa sobre els subsistemes de logging;javax.xml.parsersho és sobre la maquinària de parseig XML. - Façana + Adapter, el duo de frontera: de portes enfora, una façana simplifica el que és nostre per a altres; un adaptador tradueix l'aliè per a nosaltres. A la frontera de tot sistema sa solen trobar-se tots dos, cadascun mirant a un costat.
Quan usar-lo i quan no
Usa'l quan:
- Diversos clients repeteixen la mateixa coordinació d'un subsistema (el símptoma del checkout: flux copiat amb divergències).
- Vols estratificar: definir punts d'entrada per capa o mòdul i reduir l'acoblament entre subsistemes.
- Un flux de negoci delicat (ordre, transaccionalitat, errors) mereix viure escrit una sola vegada i amb tests propis.
- Publiques una API —interna entre equips o externa a partners— i necessites una superfície estable que desacobli els consumidors de la teva evolució interna.
No l'usis quan:
- Hi ha un sol client i una sola crida simple: embolcallar dues crides en una classe nova és indirecció sense renda (KISS).
- L'usaries per amagar un mal disseny: si el subsistema és un nus incomprensible, la façana només hi posa una cortina al davant; el nus continua creixent al darrere. Primer ordena (potser amb els altres patrons del mòdul), després, si encara aporta, façana.
- Cada client necessita un flux diferent: una façana amb quinze variants de
confirmarComanda(...)sobrecarregades és la duplicació original, ara centralitzada i amb paràmetres booleans.
Relació amb altres patrons (només menció): Adapter tradueix una interfície, Facade en simplifica moltes — l'acarament fi, a la comparativa; la façana sol crear-se amb les fàbriques del mòdul 2 i ser instància única compartida (Singleton o, millor, injecció); Mediator també centralitza interaccions, però entre col·legues que es comuniquen entre si i amb trànsit d'anada i tornada — el distingirem al mòdul 4; i dins del subsistema, la façana conviu amb tot el que hem vist sense fricció.
Errors Comuns i Consells
- La façana que s'engreixa. Cada sprint algú afegeix "un metodet més" i al cap de dos anys
FacanaCheckoutté 40 mètodes i lògica pròpia. Vigila la bàscula: si creixen els casos d'ús, crea façanes per àrea (checkout, seguiment, valoracions), no una façana universal. - Filtrar tipus interns. Retornar
ResultatPagamento entitats JPA del subsistema a través de la façana torna a acoblar els clients amb el que volíem amagar. La frontera defineix els seus propis tipus (ConfirmacioComanda), igual que exigim als adaptadors. - Façana obligatòria per dogma. Prohibir tot accés directe al subsistema "perquè hi ha façana" obliga a duplicar-hi operacions legítimes de baix nivell. Recorda: el patró simplifica el cas comú; no jura exclusivitat.
- Lògica de negoci de contraban. El
if (resultat.esAcceptat())és coordinació (decidir el flux); unif (client.esVip()) descompte = ...seria negoci (decidir regles) i pertany al subsistema. La línia és fina: pregunta't si la regla tindria sentit també invocada des d'un altre flux — si sí, cap avall amb ella. - No testejar la façana com a flux. La façana és el lloc natural dels tests d'orquestració: amb dobles dels cinc serveis, verifica l'ordre, el tall quan el pagament falla, la no-notificació en error. Si aquests tests no existeixen, el flux més delicat del negoci està sense xarxa.
- Consell: dissenya la interfície de la façana des del client, no des del subsistema: primer escriu com vols que es llegeixi el codi de l'app (
confirmarComanda(cistella, pagament)), i després fes que la façana ho compleixi. Les façanes dissenyades des de dins acaben fent olor de les tripes que havien d'ocultar.
Exercicis
Exercici 1: la façana de seguiment
La pantalla "on és la meva comanda?" necessita: estat de la comanda (ServeiCuina.estatDe(numeroComanda)), posició i nom del repartidor si ja ha sortit (ServeiRepartiment.repartimentDe(numeroComanda)), i hora estimada recalculada (CalculadoraETA.estima(comanda, repartiment)). Avui les tres crides i la seva combinació estan copiades a iOS, Android i web. Dissenya FacanaSeguiment: firma pública, tipus de retorn propi de la frontera i esquelet d'implementació.
Exercici 2: la façana impostora
Quines dues violacions del patró conté aquesta versió i per què són perilloses?
public class FacanaCheckout {
private int comandesConfirmadesAvui = 0; // (a)
public ConfirmacioComanda confirmarComanda(Cistella cistella, DadesPagament dades) {
if (cistella.getClient().esVip() && comandesConfirmadesAvui < 100) {
cistella.aplicarDescompte(new BigDecimal("0.05")); // (b)
}
// ... resta del flux com abans ...
comandesConfirmadesAvui++;
// ...
}
}Exercici 3: accés directe o ampliar la façana?
L'equip de comptabilitat necessita, per al seu tancament diari, la llista de transaccions de pagament del dia tal com les retorna la passarel·la. Discuteix: han de demanar-la a través de FacanaCheckout o anar directes al subsistema de pagaments? Dóna un criteri general reutilitzable.
Solucions
Solució 1:
public class FacanaSeguiment {
private final ServeiCuina cuina;
private final ServeiRepartiment repartiment;
private final CalculadoraETA eta;
public FacanaSeguiment(ServeiCuina cuina, ServeiRepartiment repartiment, CalculadoraETA eta) {
this.cuina = cuina; this.repartiment = repartiment; this.eta = eta;
}
/** Tipus propi de la frontera: nomes el que la pantalla necessita. */
public record EstatComandaClient(String fase, String nomRepartidor,
Posicio posicioRepartidor, LocalTime horaEstimada) {}
public EstatComandaClient estatDe(NumeroComanda numero) {
EstatComanda comanda = cuina.estatDe(numero);
Optional<Repartiment> enCurs = repartiment.repartimentDe(numero);
LocalTime estimada = eta.estima(comanda, enCurs.orElse(null));
return new EstatComandaClient(
comanda.faseLlegible(),
enCurs.map(Repartiment::getNomRepartidor).orElse(null),
enCurs.map(Repartiment::getPosicio).orElse(null),
estimada);
}
}Claus: una sola crida per als tres clients, retorn propi (EstatComandaClient, un record de frontera) en lloc de filtrar EstatComanda/Repartiment, i la combinació (què passa si encara no hi ha repartidor) escrita una vegada.
Solució 2: (a) Estat propi: comandesConfirmadesAvui converteix la façana en propietària d'una dada de negoci que ningú més no veu — es perd en reiniciar, falla amb diverses instàncies i duplica una veritat que pertany al repositori. (b) Lògica de negoci de contraban: la regla del descompte VIP viu on ningú no la buscarà (la trobarà l'equip de promocions quan canviï la política?) i és invisible per a qualsevol altre flux que confirmi comandes (l'API de partners no l'aplicaria: preus inconsistents). Totes dues coses a més fan la façana difícil de substituir o testejar, que era la seva gràcia: ha de tornar a ser coordinació pura i les regles, baixar al subsistema (servei de promocions, repositori).
Solució 3: directes al subsistema de pagaments (o millor, a una façana de l'àrea de pagaments si existeix). Criteri reutilitzable: la façana serveix els casos d'ús comuns dels seus clients, al seu nivell d'abstracció; comptabilitat demana dades crues d'un subsistema concret, en el vocabulari d'aquell subsistema, per a un cas d'ús aliè al checkout. Ampliar FacanaCheckout amb transaccionsDelDia() la desviaria del seu propòsit i l'engreixaria (error "façana que s'engreixa"). El patró no prohibeix l'accés directe: usa'l quan el cas d'ús no és de la façana.
Conclusió
Facade posa una porta simple davant d'un subsistema complex: una classe que ofereix les operacions d'alt nivell que els clients realment necessiten, escriu el flux delicat una sola vegada i redueix l'acoblament d'un graf a un embut. Saps també el que no és: ni prohibició d'accés directe, ni magatzem d'estat, ni penjador per a lògica de negoci. A PideYa, FacanaCheckout va reunir validació, impostos per mercat, cobrament, construcció de la comanda i notificació — tot reutilitzant peces d'aquest mòdul i de l'anterior, que és el millor senyal que el sistema està ben compost.
Canviem ara de preocupació per complet: de l'ordre a l'escala. El mapa en temps real de PideYa dibuixa milers de repartidors i restaurants, i cada marcador arrossega la seva icona, els seus bytes, la seva memòria... multiplicats per deu mil. Quan els objectes són multitud, la pregunta estructural és una altra: quant del que cada objecte carrega és realment seu, i quant podria compartir-se entre tots? Ens veiem a Flyweight.
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
