La logística de PideYa és una conversa a tres bandes que no para: la cuina acaba una comanda i busca repartidor; el repartidor acaba un lliurament i pregunta si hi ha res pendent; una comanda porta massa temps esperant i cal reassignar-la. Al codi actual, cuina, comandes i repartidors es coneixen i es criden entre si: una malla on cada classe guarda referències a les altres, i on afegir una peça (arriben els repartidors en moto amb nevera!) obliga a tocar-ho tot. Mediator proposa refer la topologia: que cap col·lega no parli amb un altre directament i tota la coordinació passi per un objecte central — convertir la malla en una estrella. I amb l'honestedat de sempre: veurem també com aquesta estrella pot degenerar en el temut mediador-déu.

Contingut

  1. El problema a PideYa: la malla del repartiment
  2. Malla vs. estrella: el compte de l'acoblament
  3. Intenció i estructura del patró
  4. Implementació Java completa: CentralRepartiment
  5. La conversa en el temps
  6. Variants del patró
  7. El risc del mediador-déu
  8. Quan usar-lo i quan no
  9. Relació amb altres patrons
  10. Errors comuns
  11. Exercicis i conclusió

El problema a PideYa: la malla del repartiment

L'estat actual, resumit en tres classes que s'abracen:

public class Cuina {
    private List<Repartidor> repartidors;            // coneix els repartidors

    public void comandaAcabada(Comanda comanda) {
        for (Repartidor r : repartidors) {           // i els recorre ella mateixa
            if (r.estaDisponible() && r.acceptaZona(comanda.getZona())) {
                r.assignar(comanda);
                return;
            }
        }
        comanda.marcarEsperantRepartidor();          // i decideix que passa si no n'hi ha
    }
}

public class Repartidor {
    private Cuina cuina;                             // coneix la cuina

    public void lliuramentCompletat() {
        this.disponible = true;
        Comanda pendent = cuina.algunaComandaEsperant(this.zona);  // i li pregunta
        if (pendent != null) {
            assignar(pendent);
        }
    }
}

Els símptomes, un a un:

  • Tothom coneix tothom: Cuina guarda repartidors, Repartidor guarda la cuina, i el monitor de comandes encallades coneixerà tots dos. Cada referència creuada és una dependència de compilació i un new/setter que algú manté.
  • La lògica de coordinació està repartida: part de "com s'assigna un repartidor" viu a Cuina, part a Repartidor. Per entendre el protocol complet cal llegir totes les classes; per canviar-lo, tocar-les totes.
  • Reutilització impossible: un Repartidor no es pot provar ni reutilitzar sense una Cuina de debò penjant-ne.
  • Créixer fa mal quadràticament: afegir el monitor d'encallaments, o repartidors externs d'una subcontracta, significa noves referències creuades a les classes existents.

I fixa't en el que no és el problema: cada classe fa bé la seva feina individual (cuinar, repartir). El que sobra és que a més carreguin amb el protocol de coordinació. Aquest protocol demana un amo únic.

Malla vs. estrella: el compte de l'acoblament

Amb n participants que col·laboren tots amb tots, la malla té fins a n(n−1)/2 connexions; l'estrella, exactament n. Amb 4 col·legues: 6 contra 4; amb 8: 28 contra 8. Però més important que el compte és qui sap què: a l'estrella, cada col·lega només coneix el mediador — afegir el cinquè col·lega no toca els altres quatre.

flowchart LR
    subgraph Malla["Malla: tothom coneix tothom"]
        C1[Cuina] --- R1[Repartidors]
        C1 --- P1[Comandes llestes]
        C1 --- M1[Monitor encallaments]
        R1 --- P1
        R1 --- M1
        P1 --- M1
    end
    subgraph Estrella["Estrella: tothom coneix el mediador"]
        C2[Cuina] --- X[CentralRepartiment]
        R2[Repartidors] --- X
        P2[Comandes llestes] --- X
        M2[Monitor encallaments] --- X
    end

Intenció i estructura del patró

Intenció (GoF): definir un objecte que encapsula com interactua un conjunt d'objectes. Mediator promou el baix acoblament evitant que els objectes es refereixin els uns als altres explícitament, i permet variar-ne la interacció de manera independent.

classDiagram
    class MediadorRepartiment {
        <<interface>>
        +comandaLlesta(comanda: Comanda)
        +repartidorDisponible(r: Repartidor)
        +comandaEncallada(comanda: Comanda)
    }
    class CentralRepartiment {
        -enEspera: Queue~Comanda~
        -disponibles: List~Repartidor~
        +comandaLlesta(comanda: Comanda)
        +repartidorDisponible(r: Repartidor)
        +comandaEncallada(comanda: Comanda)
    }
    class Cuina {
        -central: MediadorRepartiment
        +acabarComanda(c: Comanda)
    }
    class Repartidor {
        -central: MediadorRepartiment
        +lliuramentCompletat()
        +assignar(c: Comanda)
    }

    MediadorRepartiment <|.. CentralRepartiment
    Cuina --> MediadorRepartiment : avisa
    Repartidor --> MediadorRepartiment : avisa
    CentralRepartiment --> Repartidor : coordina
    CentralRepartiment ..> Cuina : coordina
Rol GoF A PideYa
Mediator (interfície de coordinació) MediadorRepartiment
ConcreteMediator (el protocol sencer) CentralRepartiment
Colleague (participants que només coneixen el mediador) Cuina, Repartidor (i demà MonitorEncallaments, RepartidorExtern...)

La regla d'or que defineix el patró: els col·legues mai no es referencien entre si. Un col·lega fa la seva feina local i, quan passa alguna cosa rellevant per a altres, ho explica al mediador; el mediador decideix a qui toca reaccionar i li ho ordena.

Implementació Java completa: CentralRepartiment

La interfície del mediador. Els seus mètodes són els esdeveniments que els col·legues comuniquen — anomena'ls pel que ha passat, no pel que cal fer (això ho decideix el mediador):

public interface MediadorRepartiment {
    void comandaLlesta(Comanda comanda);
    void repartidorDisponible(Repartidor repartidor);
    void comandaEncallada(Comanda comanda);
}

Els col·legues, ara lleugers: fan la seva i avisen. Repartidor ja no coneix Cuina; Cuina ja no recorre repartidors:

public class Cuina {

    private final MediadorRepartiment central;

    public Cuina(MediadorRepartiment central) {        // injectat, com sempre (DIP)
        this.central = central;
    }

    public void acabarComanda(Comanda comanda) {
        comanda.marcarLlestaPerRecollir();             // feina propia
        central.comandaLlesta(comanda);                // avis al mediador; fi
    }
}

public class Repartidor {

    private final MediadorRepartiment central;
    private final String zona;
    private boolean disponible = true;

    public Repartidor(MediadorRepartiment central, String zona) {
        this.central = central;
        this.zona = zona;
    }

    public void lliuramentCompletat() {
        this.disponible = true;                        // feina propia
        central.repartidorDisponible(this);            // avis; ni idea de que passara
    }

    /** Ordre que rep DEL mediador. */
    public void assignar(Comanda comanda) {
        this.disponible = false;
        this.comandaActual = comanda;
        // recollir, navegar, etc.
    }

    public boolean estaDisponible() { return disponible; }
    public boolean acceptaZona(String z) { return zona.equals(z); }
}

El mediador concret: aquí, i només aquí, viu el protocol complet de coordinació — llegible de dalt a baix en una classe:

public class CentralRepartiment implements MediadorRepartiment {

    private final Queue<Comanda> enEspera = new ArrayDeque<>();
    private final List<Repartidor> flota = new ArrayList<>();

    public void registrarRepartidor(Repartidor r) {
        flota.add(r);
    }

    @Override
    public void comandaLlesta(Comanda comanda) {
        Optional<Repartidor> candidat = cercarDisponible(comanda.getZona());
        if (candidat.isPresent()) {
            candidat.get().assignar(comanda);
        } else {
            enEspera.add(comanda);                     // el protocol decideix que fer
            RegistreEsdeveniments.INSTANCIA.info("Comanda " + comanda.getId() + " en espera");
        }
    }

    @Override
    public void repartidorDisponible(Repartidor repartidor) {
        enEspera.stream()
                .filter(c -> repartidor.acceptaZona(c.getZona()))
                .findFirst()
                .ifPresent(c -> {
                    enEspera.remove(c);
                    repartidor.assignar(c);
                });
    }

    @Override
    public void comandaEncallada(Comanda comanda) {
        // reassignacio: tornar a la cua amb prioritat, avisar suport...
        enEspera.add(comanda);
    }

    private Optional<Repartidor> cercarDisponible(String zona) {
        return flota.stream()
                .filter(Repartidor::estaDisponible)
                .filter(r -> r.acceptaZona(zona))
                .findFirst();
    }
}

Repara en el que s'ha guanyat:

  • El protocol és un sol text: "si hi ha repartidor a la zona, assignar; si no, encuar; quan algú s'alliberi, mirar la cua". Abans estava escampat en tres classes.
  • Canviar el criteri d'assignació ("el més proper" en comptes d'"el primer disponible") toca una línia d'una classe. De fet, aquest criteri és una família d'algorismes intercanviables... exactament el que Strategy reïficarà a la seva lliçó — el mediador en serà el client perfecte.
  • Testejar Repartidor ara és trivial: se li injecta un MediadorRepartiment fals i prou.
  • Afegir un col·lega nou (el MonitorEncallaments que detecta esperes llargues i crida comandaEncallada) no toca ni Cuina ni Repartidor.

La conversa en el temps

El mateix escenari del principi — un repartidor s'allibera i hi ha una comanda esperant — ara en estrella:

sequenceDiagram
    participant K as Cuina
    participant C as CentralRepartiment
    participant R as Repartidor (Maria)
    K->>C: comandaLlesta(comanda 88)
    C->>C: disponible a la zona? no → encuar
    Note over R: La Maria acaba un altre lliurament
    R->>C: repartidorDisponible(Maria)
    C->>C: res en espera per a la seva zona? si: la 88
    C->>R: assignar(comanda 88)
    Note over K,R: Cuina i la Maria no s'han parlat mai

Variants del patró

  • Amb o sense interfície Mediator: si només existirà una central, GoF admet saltar-se la interfície i usar la classe concreta. Nosaltres la mantenim per testabilitat (mediadors falsos als tests dels col·legues) i perquè una CentralRepartimentExterna (subcontracta) és plausible.
  • Com avisa el col·lega el mediador? La nostra versió usa mètodes específics (comandaLlesta(...)), clara i tipada. L'alternativa és un mètode únic genèric (notificar(collega, "COMANDA_LLESTA", dades)) — més flexible, menys segura; tan bon punt el vegis, ets a un pas del bus d'esdeveniments (menció a la porta d'Observer i desenvolupament al mòdul 6).
  • Mediador d'UI: l'exemple canònic del GoF és un diàleg els widgets del qual (botó, llista, camp de text) es coordinen a través del diàleg — "en marcar la casella s'habilita el botó". Si has escrit un controlador de pantalla que coordina els seus components, has escrit un mediador.

El risc del mediador-déu

La crítica clàssica al patró, i és justa: el mediador concentra l'acoblament en comptes d'eliminar-lo. Això és bo (està localitzat, llegible, canviable) fins que deixa de ser-ho: la central que a més calcula rutes, aplica bonificacions, decideix torns i envia notificacions es converteix en un objecte-déu — la taca de responsabilitat més gran del sistema, intocable per por i coll d'ampolla de tots els canvis. Senyals d'alarma i antídots:

  • El mediador fa feina de domini en comptes de coordinar → extreu aquesta feina cap als col·legues o cap a serveis (SRP: el mediador coordina, els col·legues treballen).
  • El mediador creix amb mètodes que només interessen a una parella de col·legues → potser aquesta parella mereix parlar-se directament o tenir el seu propi mediador petit.
  • Els algorismes dins del mediador varien → extreu-los com a estratègies (Strategy) i deixa al mediador l'orquestració.

La versió honesta del contracte: Mediator no redueix la complexitat del protocol — la muda a un únic lloc. Si el protocol és intrínsecament enorme, el mediador serà gran; el patró et deu ordre, no miracles.

Quan usar-lo i quan no

Usa'l quan:

  • Un conjunt d'objectes col·labora de maneres complexes i canviants, i les referències creuades es multipliquen.
  • Vols reutilitzar o testejar els col·legues solts, sense arrossegar-ne els interlocutors.
  • El protocol de coordinació mereix ser una peça amb nom propi, llegible i modificable d'una vegada.

Evita'l quan:

  • Només hi ha dos col·laboradors amb una relació simple: una crida directa (o un Observer) basta; un mediador entre dos és burocràcia.
  • El "protocol" és trivial i estable: la malla de dos fils no fa mal.
  • Estaries creant el mediador-déu des del dia u: si la classe neix amb vint responsabilitats, el problema és de repartiment de domini, no de topologia.

Relació amb altres patrons

  • Observer: l'altra manera de desacoblar comunicació — i la confusió més freqüent del mòdul. Avançament d'una línia (acarament complet a la comparativa): Observer difon "ha passat X" a qui ho vulgui sentir, sense protocol central; Mediator dirigeix un protocol amb papers coneguts. A més, un mediador es pot implementar escoltant els seus col·legues com a observador.
  • Facade: semblança superficial (un objecte davant de diversos). La façana ofereix una interfície unidireccional simplificada cap a un subsistema que no la coneix; el mediador manté un diàleg bidireccional amb col·legues que sí que el coneixen.
  • Strategy: els criteris variables del mediador (assignació de repartidor) s'extreuen com a estratègies.
  • Singleton: la temptació de fer CentralRepartiment singleton global; millor injectar-la, pels motius que ja vam patir al mòdul 2.

Errors comuns

  • Col·legues que es guarden referències entre si "només per a aquest cas": la primera referència creuada reobre la malla; a la tercera, tens malla i mediador (el pitjor de tots dos mons).
  • El mediador que treballa: si CentralRepartiment calcula la ruta o cobra el lliurament, ha deixat de coordinar. Coordinar és decidir qui i quan; el com és dels col·legues.
  • Cicles de notificació: el mediador ordena a un col·lega, el col·lega notifica el mediador, que torna a ordenar... Protegeix-te distingint "avís d'esdeveniment" (col·lega → mediador) d'"ordre" (mediador → col·lega) i no notifiquis esdeveniments des de les ordres rebudes.
  • Interfície de mediador que calca una implementació: mètodes com assignarAlPrimerDisponible(...) fixen el protocol a la interfície; anomena'ls per esdeveniments (comandaLlesta) per poder canviar la política sense tocar els col·legues.
  • Convertir-lo en calaix de sastre: "ja que tot passa per aquí, hi fico també el logging, la memòria cau i les mètriques". Cada llogater nou acosta el mediador-déu.

Exercicis

Exercici 1: col·lega nou sense tocar els vells

Implementa MonitorEncallaments: cada minut revisa les comandes en espera i, si alguna porta més de 10 minuts, crida central.comandaEncallada(comanda). A més, la central ha de notificar llavors suport amb el Notificador del mòdul 2. Indica quines classes es creen, quines es modifiquen i quines queden intactes.

Exercici 2: malla → estrella

Dibuixa (o descriu) les dependències d'aquestes quatre classes abans i després d'aplicar Mediator: Cuina, Repartidor, PantallaSeguiment (mostra al client on és la seva comanda) i CentralRepartiment. Quantes referències entre classes hi ha a cada versió?

Exercici 3: detectar el déu

Un any després, CentralRepartiment té 1.800 línies: assigna repartidors, calcula rutes amb trànsit, aplica el bo per pluja, gestiona els torns i envia les notificacions push. Proposa un repartiment: què es queda al mediador i on va cada cosa? (Anomena patrons ja vistos on apliquin.)

Solucions

Solució 1: es crea MonitorEncallaments (col·lega nou que només coneix MediadorRepartiment); es modifica només CentralRepartiment (el cos de comandaEncallada afegeix notificadorSuport.enviar(...), amb el notificador injectat al seu constructor); queden intactes Cuina, Repartidor i la interfície MediadorRepartiment (el mètode ja existia). Aquest és el dividend de l'estrella: créixer sense tocar els veïns.

Solució 2: abans (malla): Cuina→Repartidor, Repartidor→Cuina, PantallaSeguiment→Cuina, PantallaSeguiment→Repartidor — 4 referències (i creixent quadràticament amb cada col·lega). Després (estrella): Cuina→Mediador, Repartidor→Mediador, PantallaSeguiment→Mediador, i el mediador coneix els seus col·legues — la coordinació completa queda en 1 classe i cada col·lega té exactament 1 dependència, testejable amb un mediador fals.

Solució 3: es queda a CentralRepartiment únicament l'orquestració: rebre esdeveniments, consultar peces i ordenar assignacions. Se'n van: el càlcul de rutes a un ServeiRutes (col·lega/servei de domini); el criteri d'assignació i el bo per pluja a estratègies (Strategy) intercanviables que la central usa; la gestió de torns a un GestorTorns propi (la central li pregunta disponibilitat); les notificacions push als Notificador existents, invocats per la central però implementats fora — o millor encara, disparades pels canvis d'estat de la comanda via Observer, que és just la lliçó que segueix després de la propera.

Conclusió

Mediator reordena la topologia de la col·laboració: de la malla on cuina, comandes i repartidors es coneixien tots amb tots, a l'estrella on cada col·lega només coneix CentralRepartiment i el protocol de coordinació viu sencer, llegible i canviable, en un únic lloc. Has vist el compte de l'acoblament (n contra n²), la conversa en seqüència, les seves variants, i l'advertència que l'acompanya sempre: el mediador concentra l'acoblament — vigila que no s'engreixi fins a mediador-déu, extraient feina cap als col·legues i algorismes cap a estratègies.

El patró següent canvia d'escenari però no de família: del repartiment a la cistella del client. Editar la cistella és anar provant —hi afegeixo això, en trec allò, aplico un cupó— i el client vol poder tornar enrere sense que la cistella exposi les seves tripes perquè algú les fotografiï. Desar i restaurar estat sense trencar l'encapsulació: aquest equilibri delicat és exactament l'especialitat de Memento.

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