Das Mediator-Muster in Java

1. Übersicht

In diesem Artikel werfen wir einen Blick auf das Mediator-Muster, eines der GoF-Verhaltensmuster . Wir werden seinen Zweck beschreiben und erklären, wann wir ihn verwenden sollten.

Wie üblich werden wir auch ein einfaches Codebeispiel bereitstellen.

2. Mediatormuster

Bei der objektorientierten Programmierung sollten wir immer versuchen, das System so zu gestalten, dass Komponenten lose gekoppelt und wiederverwendbar sind . Dieser Ansatz erleichtert die Wartung und den Test unseres Codes.

Im wirklichen Leben müssen wir uns jedoch oft mit einer komplexen Menge abhängiger Objekte befassen. In diesem Fall kann das Mediator-Muster nützlich sein.

Ziel des Mediator-Musters ist es, die Komplexität und Abhängigkeiten zwischen eng gekoppelten Objekten, die direkt miteinander kommunizieren, zu verringern . Dies wird erreicht, indem ein Mediatorobjekt erstellt wird, das die Interaktion zwischen abhängigen Objekten übernimmt. Folglich erfolgt die gesamte Kommunikation über den Mediator.

Dies fördert eine lose Kopplung, da eine Reihe von Komponenten, die zusammenarbeiten, nicht mehr direkt interagieren müssen. Stattdessen beziehen sie sich nur auf das einzelne Mediatorobjekt. Auf diese Weise ist es auch einfacher, diese Objekte in anderen Teilen des Systems wiederzuverwenden.

3. UML-Diagramm des Mediatormusters

Betrachten wir das Muster nun visuell:

Im obigen UML-Diagramm können wir die folgenden Teilnehmer identifizieren:

  • Mediator definiert die Schnittstelle, über die die Kollegenobjekte kommunizieren
  • Der Kollege definiert die abstrakte Klasse, die einen einzigen Verweis auf den Mediator enthält
  • ConcreteMediator kapselt die Interaktionslogik zwischen Colleague Objekten
  • ConcreteColleague1 und ConcreteColleague2 kommunizieren nur über den Mediator

Wie wir sehen können, verweisen Kollegenobjekte nicht direkt aufeinander. Stattdessen wird die gesamte Kommunikation vom Mediator ausgeführt .

Folglich können ConcreteColleague1 und ConcreteColleague2 einfacher wiederverwendet werden.

Falls wir die Art und Weise ändern müssen, wie Kollegenobjekte zusammenarbeiten, müssen wir nur die ConcreteMediator- Logik ändern . Oder wir können eine neue Implementierung des Mediators erstellen .

4. Java-Implementierung

Nachdem wir eine klare Vorstellung von der Theorie haben, schauen wir uns ein Beispiel an, um das Konzept in der Praxis besser zu verstehen.

4.1. Beispielszenario

Stellen Sie sich vor, wir bauen ein einfaches Kühlsystem, das aus einem Lüfter, einem Netzteil und einem Knopf besteht. Durch Drücken der Taste wird der Lüfter entweder ein- oder ausgeschaltet. Bevor wir den Lüfter einschalten, müssen wir den Strom einschalten. Ebenso müssen wir den Strom direkt nach dem Ausschalten des Lüfters ausschalten.

Schauen wir uns nun die Beispielimplementierung an:

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 } }

Als nächstes testen wir die Funktionalität:

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

Alles scheint gut zu funktionieren. Beachten Sie aber , wie Knopf, Fan, und PowerSupplier Klassen sind eng gekoppelt . Der Knopf arbeitet direkt auf dem Fan und Fan wirkt mit den beiden Buttons und PowerSupplier.

Es wäre schwierig, die Button- Klasse in anderen Modulen wiederzuverwenden . Wenn wir unserem System ein zweites Netzteil hinzufügen müssen, müssen wir auch die Logik der Lüfterklasse ändern .

4.2. Hinzufügen des Mediator-Musters

Lassen Sie uns nun das Mediator-Muster implementieren, um die Abhängigkeiten zwischen unseren Klassen zu verringern und den Code wiederverwendbarer zu machen.

Lassen Sie uns zunächst die Mediator- Klasse vorstellen :

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(); } }

Als nächstes ändern wir die verbleibenden Klassen:

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(); } }

Lassen Sie uns noch einmal die Funktionalität testen:

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

Unser Kühlsystem funktioniert wie erwartet.

Nachdem wir nun das Mediator - Muster implementiert haben, keines des Knopfes , Fan oder PowerSupplier Klassen kommunizieren direkt . Sie haben nur einen einzigen Verweis auf den Mediator.

Wenn wir in Zukunft ein zweites Netzteil hinzufügen müssen, müssen wir lediglich die Mediator- Logik aktualisieren . Button- und Fan- Klassen bleiben unberührt.

Dieses Beispiel zeigt, wie einfach wir abhängige Objekte trennen und die Wartung unseres Systems vereinfachen können.

5. Wann wird das Mediator-Muster verwendet?

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.

Wie bei allen anderen Mustern müssen wir unseren spezifischen Anwendungsfall berücksichtigen, bevor wir das Mediator-Muster blind implementieren .

6. Fazit

In diesem Artikel haben wir etwas über das Mediator-Muster gelernt. Wir haben erklärt, welches Problem dieses Muster löst und wann wir es tatsächlich in Betracht ziehen sollten. Wir haben auch ein einfaches Beispiel für das Entwurfsmuster implementiert.

Wie immer sind die vollständigen Codebeispiele auf GitHub verfügbar.