Skip to content

Das Mediator Pattern in JavaScript – Ordnung im Objekt-Chaos

Published: at 07:00 AMSuggest Changes

Einleitung

Das Mediator Pattern ist ein Software-Entwurfsmuster, das dabei hilft, komplexe Kommunikationsprozesse zwischen mehreren Objekten zu vereinfachen. Statt dass sich einzelne Objekte direkt ansprechen, übernimmt hier eine Vermittlungsinstanz – der Mediator – die Steuerung aller Interaktionen. In JavaScript kommt dieses Muster häufig in größeren Anwendungen zum Einsatz, um den Code übersichtlicher, wartbarer und testbarer zu gestalten. Dieser Artikel erläutert dir die wichtigsten Grundlagen zum Mediator Pattern und stellt Dir Vorteile und mögliche Nachteile anhand möglichst praxisnaher CodeBeispiele vor.

Was ist das Mediator Pattern?

Wenn wir in einer Anwendung viele Module, Klassen oder Objekte haben, die miteinander interagieren, kann es schnell chaotisch werden. Jedes Objekt, das Daten von einem anderen Objekt benötigt, muss wissen, wie es mit diesem zu kommunizieren hat. Dadurch entsteht mitunter ein Netz aus Abhängigkeiten und wechselseitigen Beziehungen. Das Mediator Pattern durchbricht dieses wirre Geflecht. Statt dass jedes Objekt jedes andere kennen muss, kennt es nur noch den Mediator, der alle Kommunikationsprozesse zentral koordiniert. Man spricht hier auch von einer Entkopplung der einzelnen Objekte, weil direkte Abhängigkeiten deutlich reduziert werden.

Für Entwickler:innen bietet dieser Ansatz einen entscheidenden Vorteil: Wer an einem bestimmten Modul arbeitet, muss nicht gleichzeitig den gesamten Überblick über die restliche Code-Basis haben. Er oder sie interagiert ausschließlich mit dem Mediator und kann sicher sein, dass dieser alle notwendigen Weiterleitungen übernimmt. Auf diese Weise werden Kommunikationsstrukturen klarer, was sich gerade in größeren Projekten positiv auf die Wartbarkeit und Erweiterbarkeit des Codes auswirkt.

CodeBeispiel: Ein Chatroom als Mediator

Ein gängiges Beispiel für das Mediator Pattern in JavaScript ist ein Chatroom, in dem verschiedene Benutzer miteinander kommunizieren. Der Chatroom fungiert dabei als zentraler Vermittler (Mediator), der Nachrichten entgegennimmt und an die entsprechenden Empfänger weiterleitet.

class Chatroom {
  constructor() {
    this.users = {};
  }

  register(user) {
    this.users[user.name] = user;
    user.chatroom = this;
  }

  send(message, from, to) {
    if (to) {
      // private message
      this.users[to.name].receive(message, from);
    } else {
      // broadcast message
      for (let userName in this.users) {
        if (userName !== from.name) {
          this.users[userName].receive(message, from);
        }
      }
    }
  }
}

class User {
  constructor(name) {
    this.name = name;
    this.chatroom = null;
  }

  send(message, to) {
    this.chatroom.send(message, this, to);
  }

  receive(message, from) {
    console.log(`${from.name} an ${this.name}: ${message}`);
  }
}

// Nutzung des Mediators
const chatroom = new Chatroom();
const alice = new User('Alice');
const bob = new User('Bob');
const charlie = new User('Charlie');

// Nutzer:innen im Chatroom registrieren
chatroom.register(alice);
chatroom.register(bob);
chatroom.register(charlie);

// Nachrichten senden
alice.send("Hallo zusammen!"); // Broadcast an alle
bob.send("Hallo Alice!", alice); // Private Nachricht an Alice

In diesem Beispiel muss kein User direkt wissen, wie er die anderen Nutzer:innen erreicht. Alle Kommunikation läuft über den Chatroom, der als zentraler Vermittler fungiert. So lässt sich die Anwendung leicht erweitern – etwa, wenn weitere Funktionen für den Chatroom hinzukommen, ohne dass man an der Struktur jedes einzelnen User-Objekts etwas ändern muss.

Das Chatroom Beispiel in “klassisch” JavaScript

Um das Mediator Pattern in JavaScript nutzen zu können, bedarf es jedoch nicht zwingend der in ES6 eingeührten Klassen. Für alle, die sich mit dem klassischen Prototype Ansatz von JavaScript wohler fühlen und deshalb hier noch ein mal ohne Klassen.

// Definition des Chatroom-Konstruktors
function Chatroom() {
  this.users = {};
}

// Methoden des Chatroom über das Prototyp-Objekt
Chatroom.prototype.register = function(user) {
  this.users[user.name] = user;
  user.chatroom = this;
};

Chatroom.prototype.send = function(message, from, to) {
  if (to) {
    // private message
    this.users[to.name].receive(message, from);
  } else {
    // broadcast message
    for (let userName in this.users) {
      if (userName !== from.name) {
        this.users[userName].receive(message, from);
      }
    }
  }
};

// Definition des User-Konstruktors
function User(name) {
  this.name = name;
  this.chatroom = null;
}

// Methoden des Users über das Prototyp-Objekt
User.prototype.send = function(message, to) {
  this.chatroom.send(message, this, to);
};

User.prototype.receive = function(message, from) {
  console.log(from.name + " an " + this.name + ": " + message);
};

// -------------------------------------------
// NUTZUNG DES CHATROOMS MIT FUNKTIONEN + PROTOTYP
// -------------------------------------------

const chatroom = new Chatroom();

const alice = new User('Alice');
const bob = new User('Bob');
const charlie = new User('Charlie');

// Nutzer:innen im Chatroom registrieren
chatroom.register(alice);
chatroom.register(bob);
chatroom.register(charlie);

// Nachrichten senden
alice.send("Hallo zusammen!");
bob.send("Hallo Alice!", alice);

Einsatz des Mediator Patterns in JavaScript

Im JavaScript-Umfeld spielen Browser-Events, DOM-Manipulationen und asynchrone Vorgänge eine zentrale Rolle. Da kann es schnell unübersichtlich werden, wenn du verschiedene Komponenten oder Module implementieren möchtest, die Daten untereinander austauschen. Das Mediator Pattern bietet sich genau hier an, denn es bietet einen zentralen „Knotenpunkt“, an dem sämtliche Kommunikationsflüsse zusammenlaufen. So kann beispielsweise in einer Single Page Application (SPA) ein Mediator Module für das Laden und Rendering von Daten, das Tracking von Benutzereingaben sowie das dynamische Nachladen von Inhalten koordinieren, ohne dass sich alle Komponenten gegenseitig kennen müssen.

Vorteile des Mediator Patterns

Verbesserte Übersichtlichkeit und Entkopplung

Einer der Hauptgründe, das Mediator Pattern einzusetzen, besteht darin, dass die Kommunikation zwischen den einzelnen Modulen stark vereinfacht wird. Statt dass sich jedes Modul darum kümmern muss, wie es mit jedem anderen Modul sprechen kann, geschieht alles über einen zentralen Vermittler. Das Ergebnis ist ein überschaubarer Code, der bei Änderungen an einem Modul nicht zwangsläufig zahlreiche Anpassungen in anderen Bereichen erfordert. Durch diese reduzierte Abhängigkeit behalten Entwickler:innen leichter den Überblick, vor allem in großen JavaScript-Projekten, in denen häufig verschiedene Dateien und Module zum Einsatz kommen.

Einfacheres Testen und hohe Erweiterbarkeit

Ein weiterer entscheidender Vorteil liegt in der verbesserten Testbarkeit. Einzelne Komponenten können im Rahmen von Unit-Tests isoliert betrachtet werden, weil sie nur über ihre Schnittstelle zum Mediator Kontakt mit der Außenwelt haben. Mit Mock- oder Stub-Objekten im Testumfeld lässt sich präzise überprüfen, ob ein Modul die richtigen Befehle sendet und ob die daraufhin erwarteten Aktionen im Mediator ausgelöst werden. Gleichzeitig ermöglicht die zentrale Rolle des Mediators eine flüssige Erweiterung der Codebasis. Neue Funktionen oder Module werden lediglich einmal in den Mediator integriert, ohne dass sie dafür in alle bestehenden Module „eingefädelt“ werden müssen. Durch diesen klar definierten Zugriffspunkt bleibt selbst in größeren Projekten eine gewisse Ordnung bestehen, sodass zukünftige Anpassungen weniger Aufwand erfordern.

// Ursprünglicher Chatroom-Konstruktor, jetzt erweitert um eine Blacklist für gebannte Nutzer:innen
function Chatroom() {
  this.users = {};
  this.bannedUsers = new Set(); // Neue Datenstruktur für Gebannte
}

// Bestehende Methode zum Registrieren eines Users im Chatroom
Chatroom.prototype.register = function(user) {
  this.users[user.name] = user;
  user.chatroom = this;
};

// Erweiterte send-Methode, die gebannte Nutzer:innen berücksichtigt
Chatroom.prototype.send = function(message, from, to) {
  // Falls der/die Absender:in gebannt ist, darf er/sie keine Nachrichten mehr senden
  if (this.bannedUsers.has(from.name)) {
    console.log(from.name + " ist gebannt und darf keine Nachrichten mehr senden.");
    return;
  }

  if (to) {
    // Private Nachricht an eine/n bestimmte/n Empfänger:in
    if (!this.bannedUsers.has(to.name)) {
      this.users[to.name].receive(message, from);
    } else {
      console.log(to.name + " ist gebannt und kann keine Nachrichten empfangen.");
    }
  } else {
    // Broadcast an alle außer den/die Absender:in und Gebannte
    for (var userName in this.users) {
      if (userName !== from.name && !this.bannedUsers.has(userName)) {
        this.users[userName].receive(message, from);
      }
    }
  }
};

// Neue Methode zum Bannen eines Users
Chatroom.prototype.banUser = function(user) {
  this.bannedUsers.add(user.name);
  console.log(user.name + " wurde aus dem Chatroom verbannt.");
};

// -------------------------
// Nutzer:innen-Konstruktor
// -------------------------
function User(name) {
  this.name = name;
  this.chatroom = null;
}

User.prototype.send = function(message, to) {
  this.chatroom.send(message, this, to);
};

User.prototype.receive = function(message, from) {
  console.log(from.name + " an " + this.name + ": " + message);
};

// -------------------------
// Moderator-Konstruktor
// -------------------------
function Moderator(name) {
  // Ruft den User-Konstruktor auf, damit Moderatoren dieselben Eigenschaften haben wie ein normaler User
  User.call(this, name);
  this.isModerator = true;
}

// Der Moderator erbt alle Methoden von User
Moderator.prototype = Object.create(User.prototype);
Moderator.prototype.constructor = Moderator;

// Zusätzliche Methode nur für Moderatoren
Moderator.prototype.warnUser = function(userToWarn, reason) {
  console.log("Moderator " + this.name + " warnt " + userToWarn.name + " wegen: " + reason);
  // Optional auch als private Nachricht
  this.send("Offizielle Verwarnung: " + reason, userToWarn);
};

// -------------------------
// ANWENDUNG DES ERWEITERTEN CHATROOMS
// -------------------------
var chatroom = new Chatroom();

// Nutzer:innen erstellen
var alice = new User('Alice');
var bob = new User('Bob');
var charlie = new Moderator('Charlie');

// Registrierung im Chatroom
chatroom.register(alice);
chatroom.register(bob);
chatroom.register(charlie);

// Nachrichten austauschen
alice.send("Hallo an alle!");
bob.send("Hi Alice!", alice);

// Moderator warnt Bob
charlie.warnUser(bob, "Unangebrachter Ton");

// Bob wird gebannt
chatroom.banUser(bob);

// Gebannter Bob versucht zu schreiben
bob.send("Ich möchte mich trotzdem äußern!");

// Moderator kann weiterhin schreiben
charlie.send("Bob wurde aus dem Chatroom entfernt.");

Nachteile des Mediator Patterns

Risiko eines „Gott-Objekts“

Gerade weil der Mediator die zentrale Rolle einnimmt, besteht die Gefahr, dass er zu viele Verantwortlichkeiten übernimmt. Entwickelt sich der Mediator zu einem allmächtigen Objekt, in dem sämtliche Geschäftslogik untergebracht ist, wird er schwer wartbar und kann zu einem Single Point of Failure werden. Kleinere Fehler oder Anpassungen im Mediator können dann Auswirkungen auf das gesamte Projekt haben. Dieses Risiko lässt sich mindern, indem man die Funktionalität im Mediator bewusst einschränkt und bei Bedarf mehrere spezialisierte Mediatoren einsetzt.

Effizienzverlust und hohe Abhängigkeit vom Vermittler

In manchen Szenarien kann das Mediator Pattern zu einer unnötig großen Anzahl von Nachrichten führen, wenn jede Aktion und jeder Datenaustausch über den zentralen Vermittler laufen muss. Statt zwei eng miteinander verknüpfte Komponenten direkt miteinander kommunizieren zu lassen, geht jede noch so kleine Interaktion an den Mediator, was schnell einen beachtlichen Overhead erzeugen kann. Zusätzlich sind sämtliche Module in hohem Maße von den im Mediator definierten Schnittstellen abhängig. Ändert die oder der Entwickler:in diese Schnittstellen zu oft, entsteht rasch Anpassungsbedarf in vielen Teilen der Anwendung, was das angestrebte Maß an Unabhängigkeit erheblich einschränken kann.

Um diesen Nachteil abzumildern, ist eine gut durchdachte Architektur entscheidend. Es empfiehlt sich, die Rollen und Verantwortlichkeiten klar aufzuteilen und die Kommunikationen so zu planen, dass nur wirklich notwendige Interaktionen über den Mediator laufen. Auf diese Weise lassen sich unerwünschte Abhängigkeiten und unnötiger Overhead minimieren, während das Mediator Pattern weiterhin seine Vorteile ausspielen kann.

Fazit

Das Mediator Pattern ist ein leistungsstarkes Werkzeug in der JavaScript-Entwicklung, um komplexe Kommunikationsprozesse zu entflechten und den Code übersichtlicher zu gestalten. Besonders in größeren Projekten kann das Pattern helfen, Abhängigkeiten und Wechselwirkungen zu reduzieren und die Wartung zu vereinfachen. Allerdings solltest du darauf achten, dass aus dem Mediator kein übermächtiges Zentrum aller Logik wird. Mit einer wohlüberlegten Aufteilung der Funktionalitäten und einer klaren Rollenverteilung zwischen einzelnen Modulen und dem Mediator kannst du jedoch erheblich von diesem Muster profitieren.


FAQ

1. Wie setze ich einen einfachen Mediator in JavaScript auf?

Ein Grundgerüst kann bereits eine Klasse sein, die bestimmte Methoden zur Kommunikation anbietet. Du brauchst nur einen zentralen Mediator und Teilnehmer-Objekte.

Beispiel:

class SimpleMediator {
  notify(sender, event) {
    console.log(`Sender: ${sender}, Event: ${event}`);
  }
}

const mediator = new SimpleMediator();
mediator.notify("ModulA", "EventX");

2. Ist das Mediator Pattern das gleiche wie das Observer Pattern?

Nein, das Mediator Pattern kümmert sich aktiv um die Vermittlung zwischen Objekten, während das Observer Pattern auf Benachrichtigungen basiert, die gesendet und abonniert werden. Beim Observer Pattern reagieren Abonnenten auf Änderungen, beim Mediator Pattern steuert ein Vermittler aktiv die Kommunikation.

Beispiel:

// Observer Schema
class Observer {
  update(data) {
    console.log("Update erhalten:", data);
  }
}

3. Wann sollte ich besser kein Mediator Pattern verwenden?

Wenn die Kommunikation zwischen Objekten sehr einfach ist oder überwiegend nur in eine Richtung läuft, ist das Mediator Pattern möglicherweise ein Overkill. Eine direkte Referenz oder ein einfaches Callback kann in kleineren Anwendungen ausreichend sein.

Beispiel:

function direktKommunizieren(modulA, modulB) {
  modulB.aktionVonA();
}

4. Kann ein Mediator mit asynchronem Code umgehen?

Ja, insbesondere in JavaScript ist asynchroner Code weit verbreitet. Du kannst async/await oder Promises im Mediator einsetzen, damit dieser beispielsweise auf Datenbank-Abfragen oder API-Calls wartet, bevor er die nächste Aktion auslöst.

Beispiel:

class AsyncMediator {
  async fetchDataAndNotify(url, sender) {
    const response = await fetch(url);
    const data = await response.json();
    console.log(`Daten von ${sender} geladen:`, data);
  }
}

5. Wie teste ich einen Mediator am besten?

Du kannst den Mediator in Isolation testen, indem du Mock-Objekte oder Spies einsetzt, um zu überprüfen, ob die richtigen Methoden aufgerufen werden. Bei größeren Projekten helfen dir auch Integrationstests weiter, um die Zusammenarbeit mehrerer Komponenten zu testen.

Beispiel (Pseudo-Testcode):

const mediator = new Chatroom();
const mockUser = { name: 'TestUser', receive: jest.fn() };
mediator.register(mockUser);
// Test, ob "receive" aufgerufen wurde, wenn eine Nachricht gesendet wird

6. Können mehrere Mediatoren in einer Anwendung sinnvoll sein?

Ja, das ist sogar empfehlenswert, wenn du unterschiedliche Verantwortlichkeiten hast. So lassen sich Themenbereiche voneinander trennen, ohne dass ein einzelner Mediator zu groß und komplex wird.

Beispiel:

const uiMediator = new UIMediator();
const dataMediator = new DataMediator();
// UI-spezifische Kommunikation getrennt von Datenverarbeitung

7. Wie kann ich den Mediator im Frontend nutzen?

Du kannst einen Mediator einsetzen, um DOM-Events, Benutzeraktionen und AJAX-Requests zu koordinieren. So stellst du sicher, dass sich die einzelnen Komponenten (z. B. Formulare, Buttons, Tabs) nicht direkt gegenseitig aufrufen, sondern stattdessen nur mit dem Mediator sprechen.

Beispiel:

class UIMediator {
  handleClick(buttonId) {
    console.log(`Button ${buttonId} geklickt.`);
  }
}

8. Gibt es in Vanilla JavaScript eine eingebaute Unterstützung für das Mediator Pattern?

Nein, JavaScript bietet keine direkte Sprachfunktionalität für das Mediator Pattern. Du baust es dir selber, indem du Klassen oder Funktionen nutzt, die eine zentrale Vermittlung ermöglichen. Das macht das Pattern flexibel, da du es an deine Bedürfnisse anpassen kannst.

Beispiel:

function createMediator() {
  const participants = [];
  return {
    register(participant) { participants.push(participant); },
    notify(sender, msg) { /* ... */ }
  };
}

9. Wie verhindere ich, dass mein Mediator-Objekt in reinem JavaScript zu groß wird?

Teile den Mediator in mehrere Instanzen auf, falls du verschiedene Domänen oder Module hast, die kaum miteinander interagieren. So behältst du eine übersichtliche Struktur und vermeidest, dass ein einzelner Mediator zum unübersichtlichen „Alleskoordinator“ wird.

// Verschiedene Mediatoren für unterschiedliche Zwecke
const formMediator = new FormMediator();
const notificationMediator = new NotificationMediator();

10. Wie kann ich sicherstellen, dass mein Mediator nicht zum Engpass wird?

Du kannst eine klare Struktur für deinen Mediator festlegen, ihn in Teilbereiche aufsplitten und ihn nur für das verwenden, wofür er wirklich gedacht ist – nämlich die Kommunikation zu vermitteln. Bleibe bei einzelnen Verantwortungsbereichen für jeden Mediator und verschiebe keine unnötigen Logiken hinein.

Beispiel:

// Ausgelagerte Logik in eigene Klassen
class ValidationService {
  validate(data) { /* ... */ }
}

class FormMediator {
  constructor(validationService) {
    this.validationService = validationService;
  }
  submitForm(data) {
    if (this.validationService.validate(data)) {
      console.log("Formular validiert und gesendet.");
    }
  }
}

Damit hast du einen Überblick darüber, wie das Mediator Pattern in JavaScript eingesetzt wird, welche Vor- und Nachteile es bietet und wie du es in der Praxis nutzen kannst. Nutze diese Vorgehensweise, um in deinen eigenen Projekten für eine saubere, nachvollziehbare Struktur zu sorgen.


Buy me a coffee

Wenn Dir meine Beiträge gefallen und sie Dir bei Deiner Arbeit helfen, würde ich mich über einen “Kaffee” und ein paar nette Worte von Dir freuen.

Buy me a coffee



Previous Post
Das Command Pattern in JavaScript – Struktur, Vorteile und Praxisbeispiele
Next Post
Decorator Pattern in JavaScript – Elegantes Erweiteren von Funktionen und Objekten