Mediator Pattern

Mediator Pattern

vg

Das Mediator Pattern ist ein Verhaltensmuster, das die Kommunikation zwischen Objekten erleichtert, indem es die direkte Interaktion zwischen ihnen vermeidet. Stattdessen erfolgt die Kommunikation über einen zentralen Vermittler. Dadurch wird die Kopplung zwischen den Objekten verringert und die Wartbarkeit verbessert.

Was ist das Mediator Pattern?

Das Mediator Pattern fördert die Entkopplung von Objekten, indem es einen Mediator einführt. Dieser fungiert als Vermittler, der die Kommunikation zwischen Objekten verwaltet. Anstatt direkt miteinander zu kommunizieren, senden die Objekte ihre Anfragen an den Mediator, der diese weiterleitet. So wird die Abhängigkeit zwischen den Objekten reduziert.

Komponenten des Mediator Patterns

Das Mediator Pattern besteht aus mehreren wichtigen Komponenten:

  1. Mediator: Ein Interface oder eine abstrakte Klasse, die die Kommunikation zwischen den Objekten definiert. Der Mediator empfängt Nachrichten von den Objekten und leitet diese weiter.
  2. ConcreteMediator: Eine konkrete Implementierung des Mediators. Sie verwaltet die Kommunikation zwischen den verschiedenen Objekten und übernimmt die Logik der Weiterleitung.
  3. Colleague: Objekte, die miteinander interagieren, aber über den Mediator kommunizieren. Sie sind vom Mediator abhängig, um Nachrichten zu empfangen und zu senden.
  4. ConcreteColleague: Eine konkrete Implementierung eines Objekts, das mit anderen Objekten über den Mediator kommuniziert.

Beispiel des Mediator Patterns in C++

Ein praktisches Beispiel des Mediator Patterns ist ein Chat-System, in dem Benutzer Nachrichten an den Mediator senden, der diese dann an andere Benutzer weiterleitet.

#include <iostream>
#include <string>
#include <vector>
#include <memory>

// Mediator: Abstrakte Klasse für den Mediator
class Mediator {
public:
    virtual void sendMessage(const std::string& message, class Colleague* colleague) = 0;
    virtual ~Mediator() = default;
};

// Colleague: Abstrakte Klasse für die Kollegen
class Colleague {
protected:
    Mediator* mediator;
    
public:
    Colleague(Mediator* mediator) : mediator(mediator) {}
    virtual void receiveMessage(const std::string& message) = 0;
    virtual ~Colleague() = default;
};

// ConcreteColleague: Konkrete Implementierung eines Kollegen
class ConcreteColleague : public Colleague {
private:
    std::string name;
    
public:
    ConcreteColleague(Mediator* mediator, const std::string& name)
        : Colleague(mediator), name(name) {}

    void sendMessage(const std::string& message) {
        std::cout << name << " sends message: " << message << std::endl;
        mediator->sendMessage(message, this);
    }

    void receiveMessage(const std::string& message) override {
        std::cout << name << " receives message: " << message << std::endl;
    }
};

// ConcreteMediator: Konkrete Implementierung des Mediators
class ConcreteMediator : public Mediator {
private:
    std::vector<Colleague*> colleagues;
    
public:
    void addColleague(Colleague* colleague) {
        colleagues.push_back(colleague);
    }

    void sendMessage(const std::string& message, Colleague* colleague) override {
        for (auto c : colleagues) {
            // Senden Sie die Nachricht nur an die anderen Kollegen
            if (c != colleague) {
                c->receiveMessage(message);
            }
        }
    }
};

// Client-Code
int main() {
    // Erstellen des Mediators
    ConcreteMediator mediator;
    
    // Erstellen der Kollegen und Hinzufügen zum Mediator
    ConcreteColleague colleague1(&mediator, "Alice");
    ConcreteColleague colleague2(&mediator, "Bob");
    
    mediator.addColleague(&colleague1);
    mediator.addColleague(&colleague2);
    
    // Senden von Nachrichten
    colleague1.sendMessage("Hallo Bob, wie geht's?");
    colleague2.sendMessage("Hallo Alice, mir geht's gut. Und dir?");
    
    return 0;
}

Erklärung des C++-Beispiels

  1. Mediator: Die abstrakte Klasse Mediator definiert die Methode sendMessage(), die vom konkreten Mediator implementiert wird. Diese Methode empfängt Nachrichten von den Kollegen und leitet sie weiter.
  2. ConcreteMediator: Die Klasse ConcreteMediator ist die konkrete Implementierung des Mediators. Sie enthält eine Liste von Kollegen und stellt sicher, dass Nachrichten an alle anderen Kollegen weitergeleitet werden.
  3. Colleague: Die abstrakte Klasse Colleague repräsentiert die Objekte, die miteinander kommunizieren müssen. Sie haben eine Referenz auf den Mediator, über den sie Nachrichten senden und empfangen.
  4. ConcreteColleague: Die Klasse ConcreteColleague ist die konkrete Implementierung eines Kollegen. Sie kann Nachrichten an den Mediator senden und empfängt Nachrichten vom Mediator.
  5. Client: Der Client erstellt den Mediator und die Kollegen und fügt sie dem Mediator hinzu. Die Kollegen können dann Nachrichten an den Mediator senden, der diese an die anderen Kollegen weiterleitet.

Beispiel des Mediator Patterns in Python

Das Mediator-Pattern ist ein Entwurfsmuster, das die Kommunikation zwischen Objekten kapselt, um direkte Abhängigkeiten zwischen den Objekten zu vermeiden. Es erlaubt den Objekten, miteinander zu interagieren, ohne voneinander zu wissen, was die Kopplung reduziert und die Wartbarkeit verbessert.

Hier ist ein einfaches Beispiel des Mediator-Patterns in Python:

Beispiel: Mediator-Pattern in einem Chat-System

Angenommen, wir haben mehrere Benutzer, die in einem Chatraum miteinander kommunizieren, aber jeder Benutzer kommuniziert nicht direkt mit anderen Benutzern, sondern über einen Chat-Mediator.

Mediator:

class ChatMediator:
    def __init__(self):
        self.users = []

    def add_user(self, user):
        self.users.append(user)
        
    def send_message(self, sender, message):
        for user in self.users:
            if user != sender:
                user.receive_message(message)

Benutzer:

class User:
    def __init__(self, name, mediator):
        self.name = name
        self.mediator = mediator

    def send_message(self, message):
        print(f"{self.name} sends message: {message}")
        self.mediator.send_message(self, message)
        
    def receive_message(self, message):
        print(f"{self.name} received message: {message}")

Anwendung des Mediator-Patterns:

# Mediator erstellen
chat_mediator = ChatMediator()

# Benutzer erstellen und zum Mediator hinzufügen
alice = User("Alice", chat_mediator)
bob = User("Bob", chat_mediator)
charlie = User("Charlie", chat_mediator)

chat_mediator.add_user(alice)
chat_mediator.add_user(bob)
chat_mediator.add_user(charlie)

# Alice sendet eine Nachricht
alice.send_message("Hi, everyone!")

# Bob sendet eine Nachricht
bob.send_message("Hello, Alice!")

Ausgabe:

Alice sends message: Hi, everyone!
Alice received message: Hi, everyone!
Bob received message: Hi, everyone!
Charlie received message: Hi, everyone!
Bob sends message: Hello, Alice!
Alice received message: Hello, Alice!
Charlie received message: Hello, Alice!

Erklärung:

  • ChatMediator: Der Mediator, der für die Kommunikation zwischen den Benutzern zuständig ist. Er empfängt Nachrichten von einem Benutzer und leitet sie an alle anderen Benutzer weiter.
  • User: Jeder Benutzer hat eine send_message-Methode, mit der er Nachrichten an den Mediator sendet. Der Mediator kümmert sich um die Weiterleitung der Nachrichten an alle anderen Benutzer.

Mit diesem Ansatz haben die Benutzer keine direkte Kenntnis von den anderen Benutzern, sondern kommunizieren nur über den Mediator. Dadurch wird die Kopplung zwischen den Benutzern reduziert, und Änderungen am Kommunikationsmechanismus müssen nur im Mediator vorgenommen werden, ohne den Code der einzelnen Benutzer zu beeinflussen.

Vorteile des Mediator Patterns

  1. Entkopplung: Der Mediator reduziert die Kopplung zwischen den Objekten, da sie nur mit dem Mediator kommunizieren und nicht direkt miteinander.
  2. Vereinfachte Kommunikation: Die Kommunikation zwischen Objekten wird vereinfacht, da der Mediator als zentraler Punkt der Interaktion fungiert.
  3. Flexibilität: Neue Kollegen können leicht hinzugefügt werden, ohne dass Änderungen an den bestehenden Objekten vorgenommen werden müssen.
  4. Wartbarkeit: Da die Logik der Kommunikation in den Mediator verschoben wird, wird die Wartung des Systems vereinfacht. Änderungen an der Kommunikation betreffen nur den Mediator.

Nachteile des Mediator Patterns

  1. Komplexität: In großen Systemen kann der Mediator zu einer zentralen Stelle werden, die zu komplex ist und viele Verantwortlichkeiten übernimmt.
  2. Single Point of Failure: Der Mediator kann ein Single Point of Failure werden, da alle Kommunikationen durch ihn laufen.
  3. Leistungseinbußen: In Systemen mit vielen Kollegen kann der Mediator zu einem Bottleneck werden, da er jede Nachricht weiterleiten muss.

Wann sollte das Mediator Pattern eingesetzt werden und wann nicht?

Das Mediator Pattern sollte in folgenden Szenarien eingesetzt werden:

1. Reduzierung der Komplexität bei direkter Kommunikation zwischen Objekten

Wenn mehrere Objekte miteinander direkt kommunizieren müssen, kann dies schnell zu einem unübersichtlichen und schwer wartbaren System führen. Dies wird oft als „Spaghettikopplung“ bezeichnet. Wenn jedes Objekt von jedem anderen Objekt abhängt, wird das System schwer verständlich und fehleranfällig.

Einsatzfall: Wenn ein System viele Objekte enthält, die miteinander interagieren müssen (z.B. ein Chat-System mit vielen Benutzern oder ein komplexes GUI), aber Sie vermeiden möchten, dass jedes Objekt mit jedem anderen direkt kommuniziert.

2. Vermeidung von zyklischen Abhängigkeiten

Wenn Objekte sich gegenseitig aufrufen, kann dies zu zyklischen Abhängigkeiten führen, die die Wartung erschweren und das Testen des Codes komplizieren. Ein Mediator entkoppelt diese Abhängigkeiten, indem er als zentrale Austauschstelle für alle Interaktionen dient.

Einsatzfall: Wenn Sie zyklische Abhängigkeiten zwischen Objekten vermeiden wollen. Zum Beispiel in einem GUI mit vielen Steuerungselementen (Buttons, Textfelder), bei denen ein Ereignis von einem Steuerungselement andere Elemente beeinflussen soll.

3. Zentralisierte Steuerung der Kommunikation

Das Mediator Pattern ist nützlich, wenn Sie die Kommunikation zwischen verschiedenen Objekten zentral verwalten wollen. Dadurch können Sie die Logik zur Nachrichtenweiterleitung, Ereignisbehandlung oder Synchronisierung an einem Ort zentralisieren.

Einsatzfall: Wenn Sie eine zentrale Stelle benötigen, um das Kommunikationsverhalten zwischen verschiedenen Komponenten oder Entitäten zu koordinieren, z. B. bei der Verwaltung der Interaktion in einem komplexen Workflow-System oder in einem Spiel, bei dem verschiedene Spielfiguren miteinander interagieren müssen.

4. Wartbarkeit und Erweiterbarkeit

Wenn Sie das System erweitern möchten, ohne den gesamten Code umstrukturieren zu müssen, kann das Mediator Pattern helfen, Änderungen nur an einer Stelle vorzunehmen (im Mediator), ohne die beteiligten Objekte selbst ändern zu müssen. So bleiben Ihre Komponenten flexibler und einfacher zu warten.

Einsatzfall: Wenn Ihr System in Zukunft wachsen könnte und Sie neue Objekte oder Funktionen hinzufügen müssen, ohne bestehende Objekte stark ändern zu müssen. Das Mediator Pattern ermöglicht es Ihnen, die Erweiterung an einer zentralen Stelle vorzunehmen.

5. Vermeidung von zu viel „Logik“ in den Objekten selbst

Wenn Objekte zu viele Verantwortlichkeiten übernehmen (z.B. selbstständig Nachrichten zu senden und zu empfangen, zu reagieren, und die Logik zu verwalten), kann dies den Code unübersichtlich und schwer testbar machen. Der Mediator übernimmt die Verantwortung für diese Interaktionen und sorgt dafür, dass die Objekte sich nur auf ihre eigenen Aufgaben konzentrieren.

Einsatzfall: Wenn Sie Logik aus den Objekten herausnehmen und an eine zentrale Stelle delegieren möchten, um die Verantwortlichkeiten der einzelnen Objekte zu minimieren.

6. Vermeidung von zu vielen Interaktionen zwischen Objekten

Wenn es viele verschiedene Objekte gibt, die miteinander interagieren müssen, und es schwierig wird, nachzuvollziehen, wie sie miteinander kommunizieren, kann der Mediator helfen, diese Interaktionen zu vereinfachen.

Einsatzfall: In komplexen Systemen, wie z.B. einem komplexen UI-Framework oder einem Software-System mit vielen Modulen, bei dem jede Veränderung eines Moduls die anderen beeinflussen könnte.

7. Ereignisgetriebene Architektur

In ereignisgetriebenen Systemen (z.B. bei der Verarbeitung von Ereignissen oder Benutzereingaben in einem GUI) kann der Mediator dazu dienen, Ereignisse zu koordinieren und an die richtigen Empfänger weiterzuleiten.

Einsatzfall: Wenn es viele Komponenten gibt, die auf Ereignisse reagieren müssen (z.B. eine GUI-Anwendung mit vielen interaktiven Steuerelementen wie Buttons, Dropdown-Menüs, etc.), und Sie möchten die Ereignisverarbeitung zentralisieren.


Wann nicht das Mediator Pattern einsetzen?

  • Einfachere Systeme: Wenn Ihre Anwendung relativ einfach ist und nur eine geringe Anzahl von Objekten miteinander interagieren muss, ist das Mediator Pattern möglicherweise überflüssig und fügt unnötige Komplexität hinzu.
  • Zu viele Mediatore: Wenn Sie für jede neue Interaktion einen neuen Mediator benötigen, kann dies schnell zu einem Overhead führen, da das Pattern zusätzliche Abstraktionsebenen einführt, die in kleinen, weniger komplexen Systemen unnötig sein können.
  • Unnötige Kapselung: Wenn die Interaktion zwischen Objekten einfach und direkt ist und keine Notwendigkeit besteht, sie zu entkoppeln, kann das Mediator Pattern eine unnötige Kapselung darstellen, die die Verständlichkeit des Systems verringert.

Verwenden Sie das Mediator Pattern, wenn die Komplexität von Kommunikation und Abhängigkeiten in Ihrem System steigt und Sie eine zentrale Steuerung oder Entkopplung der Interaktionen zwischen Objekten benötigen. Es hilft, das System flexibler, wartungsfreundlicher und erweiterbar zu gestalten, indem es die Verantwortung für die Kommunikation auf einen Mediator überträgt.

Fazit

Das Mediator Pattern ist ein leistungsfähiges Verhaltensmuster, das die Kommunikation zwischen Objekten verwaltet und entkoppelt. Es reduziert die Abhängigkeiten zwischen den Objekten und erleichtert so die Wartbarkeit und Erweiterbarkeit des Systems. Die Beispiele in C++ und Python zeigen, wie das Muster die Kommunikation vereinfacht und die Kopplung zwischen den Objekten minimiert. Obwohl das Muster einige Nachteile hinsichtlich der Komplexität und Leistung haben kann, ist es besonders nützlich in Szenarien, in denen viele Objekte miteinander interagieren müssen, aber ihre Interaktionen zentral gesteuert werden sollen.

Zurück zur Liste aller Pattern: Liste der Design-Pattern

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)