Il modello Mediator in Java

1. Panoramica

In questo articolo daremo uno sguardo al Mediator Pattern, uno dei pattern comportamentali di GoF . Descriveremo il suo scopo e spiegheremo quando dovremmo usarlo.

Come al solito, forniremo anche un semplice esempio di codice.

2. Modello mediatore

Nella programmazione orientata agli oggetti, dovremmo sempre cercare di progettare il sistema in modo tale che i componenti siano liberamente accoppiati e riutilizzabili . Questo approccio rende il nostro codice più facile da mantenere e testare.

Nella vita reale, tuttavia, spesso abbiamo bisogno di affrontare un insieme complesso di oggetti dipendenti. Questo è quando il modello Mediator può tornare utile.

Lo scopo del Mediator Pattern è ridurre la complessità e le dipendenze tra oggetti strettamente collegati che comunicano direttamente tra loro . Ciò si ottiene creando un oggetto mediatore che si prende cura dell'interazione tra oggetti dipendenti. Di conseguenza, tutta la comunicazione passa attraverso il mediatore.

Ciò favorisce l'accoppiamento libero, poiché un insieme di componenti che lavorano insieme non devono più interagire direttamente. Invece, si riferiscono solo al singolo oggetto mediatore. In questo modo, è anche più facile riutilizzare questi oggetti in altre parti del sistema.

3. Diagramma UML di Mediator Pattern

Diamo ora un'occhiata al pattern visivamente:

Nel diagramma UML sopra, possiamo identificare i seguenti partecipanti:

  • Mediator definisce l'interfaccia che gli oggetti Collega usano per comunicare
  • Il collega definisce la classe astratta con un unico riferimento al Mediatore
  • ConcreteMediator incapsula la logica di interazione tra gli oggetti Collega
  • ConcreteColleague1 e ConcreteColleague2 comunicano solo tramite il Mediatore

Come possiamo vedere, gli oggetti Collega non si riferiscono tra loro direttamente. Tutta la comunicazione è invece curata dal Mediatore .

Di conseguenza, ConcreteColleague1 e ConcreteColleague2 possono essere riutilizzati più facilmente.

Inoltre, nel caso in cui sia necessario modificare il modo in cui gli oggetti Collega lavorano insieme, dobbiamo solo modificare la logica ConcreteMediator . Oppure possiamo creare una nuova implementazione del Mediatore.

4. Implementazione Java

Ora che abbiamo un'idea chiara della teoria, diamo un'occhiata a un esempio per comprendere meglio il concetto nella pratica.

4.1. Scenario di esempio

Immagina di costruire un semplice sistema di raffreddamento composto da una ventola, un alimentatore e un pulsante. Premendo il pulsante si accenderà o si spegnerà la ventola. Prima di accendere la ventola, dobbiamo accendere l'alimentazione. Allo stesso modo, dobbiamo spegnere l'alimentazione subito dopo aver spento la ventola.

Diamo ora un'occhiata all'implementazione di esempio:

public class Button { private Fan fan; // constructor, getters and setters public void press(){ if(fan.isOn()){ fan.turnOff(); } else { fan.turnOn(); } } }
public class Fan { private Button button; private PowerSupplier powerSupplier; private boolean isOn = false; // constructor, getters and setters public void turnOn() { powerSupplier.turnOn(); isOn = true; } public void turnOff() { isOn = false; powerSupplier.turnOff(); } }
public class PowerSupplier { public void turnOn() { // implementation } public void turnOff() { // implementation } }

Successivamente, testiamo la funzionalità:

@Test public void givenTurnedOffFan_whenPressingButtonTwice_fanShouldTurnOnAndOff() { assertFalse(fan.isOn()); button.press(); assertTrue(fan.isOn()); button.press(); assertFalse(fan.isOn()); }

Tutto sembra funzionare bene. Ma notate come le classi Button, Fan e PowerSupplier siano strettamente collegate . Il pulsante opera direttamente sulla ventola e la ventola interagisce sia con il pulsante che con il PowerSupplier.

Sarebbe difficile riutilizzare la classe Button in altri moduli. Inoltre, se abbiamo bisogno di aggiungere un secondo alimentatore al nostro sistema, dovremmo modificare la logica della classe Fan .

4.2. Aggiunta del motivo mediatore

Ora, implementiamo il Mediator Pattern per ridurre le dipendenze tra le nostre classi e rendere il codice più riutilizzabile.

Per prima cosa, introduciamo la classe Mediator :

public class Mediator { private Button button; private Fan fan; private PowerSupplier powerSupplier; // constructor, getters and setters public void press() { if (fan.isOn()) { fan.turnOff(); } else { fan.turnOn(); } } public void start() { powerSupplier.turnOn(); } public void stop() { powerSupplier.turnOff(); } }

Successivamente, modifichiamo le classi rimanenti:

public class Button { private Mediator mediator; // constructor, getters and setters public void press() { mediator.press(); } }
public class Fan { private Mediator mediator; private boolean isOn = false; // constructor, getters and setters public void turnOn() { mediator.start(); isOn = true; } public void turnOff() { isOn = false; mediator.stop(); } }

Ancora una volta, testiamo la funzionalità:

@Test public void givenTurnedOffFan_whenPressingButtonTwice_fanShouldTurnOnAndOff() { assertFalse(fan.isOn()); button.press(); assertTrue(fan.isOn()); button.press(); assertFalse(fan.isOn()); }

Il nostro sistema di raffreddamento funziona come previsto.

Ora che abbiamo implementato il pattern Mediator, nessuna delle classi Button , Fan o PowerSupplier comunica direttamente . Hanno solo un unico riferimento al Mediatore.

Se in futuro dovremo aggiungere un secondo alimentatore, tutto ciò che dobbiamo fare è aggiornare la logica di Mediator ; Le classi Button e Fan rimangono invariate.

Questo esempio mostra quanto facilmente possiamo separare gli oggetti dipendenti e rendere il nostro sistema più facile da mantenere.

5. Quando utilizzare il modello Mediator

The Mediator Pattern is a good choice if we have to deal with a set of objects that are tightly coupled and hard to maintain. This way we can reduce the dependencies between objects and decrease the overall complexity.

Additionally, by using the mediator object, we extract the communication logic to the single component, therefore we follow the Single Responsibility Principle. Furthermore, we can introduce new mediators with no need to change the remaining parts of the system. Hence, we follow the Open-Closed Principle.

Sometimes, however, we may have too many tightly coupled objects due to the faulty design of the system. If this is a case, we should not apply the Mediator Pattern. Instead, we should take one step back and rethink the way we've modeled our classes.

Come con tutti gli altri pattern, dobbiamo considerare il nostro caso d'uso specifico prima di implementare ciecamente il Mediator Pattern .

6. Conclusione

In questo articolo, abbiamo appreso del Mediator Pattern. Abbiamo spiegato quale problema risolve questo pattern e quando dovremmo effettivamente considerare di usarlo. Abbiamo anche implementato un semplice esempio del design pattern.

Come sempre, gli esempi di codice completi sono disponibili su GitHub.