🔥 Zapisy zamknięte, ale możesz pobrać Roadmapę .NET i dołączyć do listy oczekujących — Pobierz i dołącz do Listy VIP →

Mediator Pattern w C# – implementacja czatu pokoju

Kolejny wzorzec z serii opartej na Anatomii wzorca — 8 elementach opisu GoF. Tym razem Mediator — wzorzec, który ratuje kod, gdy N obiektów zaczyna znać wszystkie pozostałe N-1 obiektów.

1. Intent (Zamierzenie)

Definiuje obiekt, który hermetyzuje sposób, w jaki grupa obiektów ze sobą współpracuje. Mediator promuje luźne powiązanie, zapobiegając bezpośrednim referencjom obiektów do siebie nawzajem, i pozwala zmieniać ich interakcje niezależnie.

Krótko: zamiast N obiektów trzymających referencje do siebie nawzajem (relacja many-to-many), każdy zna tylko jednego mediatora (relacja many-to-one). Mediator koordynuje całą resztę.

2. Also Known As (Inne nazwy)

GoF nie podaje dla Mediatora popularnych alternatywnych nazw — w praktyce bywa nazywany po prostu “koordynatorem” albo, w kontekście UI, “dialog controller”.

3. Motivation (Motywacja)

GoF opisuje okno dialogowe z wieloma widgetami: listą, polem tekstowym, przyciskami. Zaznaczenie elementu na liście powinno wypełnić pole tekstowe i włączyć przycisk “Usuń”. Gdyby każdy widget bezpośrednio wywoływał metody pozostałych, dialog stałby się gęstą siatką wzajemnych referencji — a próba użycia tego samego widgetu w innym oknie byłaby niemożliwa, bo ciągnąłby za sobą całą sieć zależności.

Mediator centralizuje tę logikę koordynacji w jednym obiekcie (“dialog director”). Widgety znają tylko mediatora — nie wiedzą nawzajem o swoim istnieniu.

4. Applicability (Kiedy stosować)

Stosuj Mediator gdy:

  • zestaw obiektów komunikuje się w złożony, ale dobrze określony sposób, a wynikające z tego zależności są chaotyczne i trudne do ogarnięcia,
  • reużycie obiektu jest trudne, bo komunikuje się z wieloma innymi obiektami na sztywno,
  • zachowanie rozproszone między kilka klas powinno być łatwe do zmiany bez tworzenia kolejnych podklas każdej z nich.

Sygnał ostrzegawczy w kodzie: klasa A wywołuje metody klasy B, B wywołuje metody C, C z powrotem wywołuje metody A — pajęczyna wzajemnych zależności, w której zmiana jednej klasy wymaga sprawdzenia wszystkich pozostałych.

5. Structure (Struktura)

┌──────────────────┐         ┌──────────────────┐
│  <<interface>>   │ <────── │  <<interface>>   │
│    Mediator      │         │     Colleague    │
│──────────────────│         │──────────────────│
│+Notify(sender,ev)│         │ +mediator        │
└──────────────────┘         └──────────────────┘
         △                            △
         │                    ┌────────┴────────┐
┌──────────────────┐  ┌──────────────────┐ ┌──────────────────┐
│ ConcreteMediator │  │ConcreteColleagueA│ │ConcreteColleagueB│
│──────────────────│  │──────────────────│ │──────────────────│
│ -colleagueA      │  │ +DoA()           │ │ +DoB()           │
│ -colleagueB      │  └──────────────────┘ └──────────────────┘
└──────────────────┘

ConcreteColleague obiekty nie znają się nawzajem — każdy zna tylko wspólny interfejs Mediator i wywołuje go, gdy chce coś zakomunikować reszcie. ConcreteMediator zna wszystkich uczestników i decyduje, kto ma zareagować na co.

6. Participants (Uczestnicy)

  • Mediator — interfejs komunikacji między obiektami Colleague.
  • ConcreteMediator — implementuje zachowanie kooperacyjne, koordynując swoich Colleague.
  • Colleague — każdy obiekt komunikuje się ze swoim mediatorem zamiast bezpośrednio z innymi kolegami.

7. Collaborations (Współpraca)

Colleague wysyła i odbiera żądania za pośrednictwem obiektu Mediator. Mediator implementuje zachowanie kooperacyjne, kierując żądania do odpowiednich Colleague.

Colleague nigdy nie wywołuje metody innego Colleague bezpośrednio — zawsze przez mediatora, który decyduje, co dalej z tym żądaniem zrobić.

8. Consequences (Konsekwencje)

Plus: ogranicza tworzenie podklas — zmiana zachowania wymaga podklasowania tylko Mediatora, nie każdego z Colleague. Plus: odseparowuje Colleague od siebie nawzajem — zależą tylko od mediatora. Plus: upraszcza protokoły komunikacji — relację wiele-do-wielu zamienia w wiele-do-jednego. Minus: mediator może stać się nadmiernie złożony — scentralizowanie logiki w jednym miejscu grozi tym, że mediator sam stanie się trudną do utrzymania “klasą, która wie wszystko”.

Ten “minus” to realne ryzyko: dobrze zaprojektowany Mediator odciąża kolegów z logiki koordynacji, ale źle zaprojektowany po prostu przenosi cały bałagan w jedno miejsce, zamiast go uporządkować.


Mediator w C# — od diagramu do kodu

Scenariusz

Pokój czatu: użytkownicy wysyłają wiadomości, które mają trafić do wszystkich pozostałych. Bez wzorca każdy Uzytkownik musiałby trzymać listę referencji do wszystkich innych uczestników — i aktualizować ją przy każdym dołączeniu/wyjściu z pokoju.

Bez wzorca — użytkownicy znają się nawzajem

public class UzytkownikZly
{
    public string Imie { get; }
    private readonly List<UzytkownikZly> _pozostali = new(); // ❌ każdy zna wszystkich

    public UzytkownikZly(string imie) => Imie = imie;

    public void Dolacz(UzytkownikZly inny) => _pozostali.Add(inny);

    public void WyslijWiadomosc(string tresc)
    {
        foreach (var u in _pozostali)
            u.Odbierz(Imie, tresc); // każdy musi wiedzieć, jak zawiadomić resztę
    }

    public void Odbierz(string od, string tresc) => Console.WriteLine($"[{Imie}] {od}: {tresc}");
}

Dodanie nowego użytkownika wymaga zarejestrowania go u wszystkich pozostałych. Usunięcie kogoś z pokoju — odrejestrowania wszędzie. UzytkownikZly zna szczegóły komunikacji, które nie powinny go obchodzić.

Z wzorcem — ChatRoom jako mediator

public interface IChatMediator
{
    void WyslijWiadomosc(string od, string tresc);
    void Dolacz(Uzytkownik uzytkownik);
}

public class ChatRoom : IChatMediator
{
    private readonly List<Uzytkownik> _uczestnicy = new();

    public void Dolacz(Uzytkownik uzytkownik) => _uczestnicy.Add(uzytkownik);

    public void WyslijWiadomosc(string od, string tresc)
    {
        foreach (var u in _uczestnicy.Where(u => u.Imie != od))
            u.Odbierz(od, tresc);
    }
}

public class Uzytkownik
{
    private readonly IChatMediator _mediator;
    public string Imie { get; }

    public Uzytkownik(string imie, IChatMediator mediator)
    {
        Imie = imie;
        _mediator = mediator;
        _mediator.Dolacz(this);
    }

    public void Wyslij(string tresc) => _mediator.WyslijWiadomosc(Imie, tresc);
    public void Odbierz(string od, string tresc) => Console.WriteLine($"[{Imie}] {od}: {tresc}");
}
var pokoj = new ChatRoom();
var anna = new Uzytkownik("Anna", pokoj);
var kuba = new Uzytkownik("Kuba", pokoj);

anna.Wyslij("Cześć wszystkim!"); // Kuba dostaje wiadomość, Anna o Kubie nic nie wie

Uzytkownik zna tylko IChatMediator — nie ma pojęcia, ilu jest innych uczestników ani kim są. Dodanie trzeciego użytkownika to jedna linijka konstruktora, zero zmian w istniejących klasach.

Mediator kontra Observer — częste pomylenie

Oba wzorce rozprzęgają obiekty, ale rozwiązują inny problem:

ObserverMediator
Kierunekjeden nadawca → wielu odbiorców (broadcast)wielu uczestników ↔ jeden koordynator (dwukierunkowo)
Celpowiadomienie o zmianie stanukoordynacja złożonej interakcji między wieloma obiektami
Kto zna kogosubject nie zna obserwatorówcolleague zna mediatora, mediator zna wszystkich colleague

W praktyce ChatRoom powyżej mógłby być zaimplementowany przez event (Observer) zamiast interfejsu Mediator — ale gdyby pokój musiał dodatkowo decydować, kto dostaje wiadomość (np. tylko użytkownicy w tym samym kanale, moderowanie treści, blokowanie spamerów), to już nie jest zwykłe powiadomienie — to logika koordynacji, czyli realny Mediator.

Pułapka: mediator jako God Object

public class SuperMediator : IChatMediator
{
    // 40 metod: WyslijWiadomosc, ZbanujUzytkownika, WalidujTresc,
    // ZapiszDoLogow, WyslijPowiadomieniePush, SprawdzUprawnienia...
}

Jeśli mediator zaczyna przejmować odpowiedzialności, które nie dotyczą koordynacji (walidacja, logowanie, uprawnienia), łamie SRP dokładnie tak samo, jak klasy, które miał rozprzęgać. Rozwiązanie: mediator koordynuje przepływ komunikatów, a konkretne zadania (walidacja, logowanie) deleguje do osobnych, wyspecjalizowanych serwisów — nie robi ich sam.

Podsumowanie

Mediator zamienia sieć wzajemnych referencji między obiektami w gwiazdę, gdzie każdy zna tylko centralny koordynator. Colleague nie muszą wiedzieć nawzajem o swoim istnieniu, co ułatwia ich reużycie i testowanie w izolacji. Uważaj na jedną pułapkę: mediator ma koordynować komunikację, nie stawać się magazynem całej logiki biznesowej systemu — inaczej sam zamieni się w problem, który miał rozwiązać.

👨‍💻
Mariusz Jurczenko
Senior .NET Developer · 10+ lat doświadczenia komercyjnego

Programista .NET z doświadczeniem komercyjnym w firmach takich jak NFZ, Kamsoft, Diagnostyka, Hermes Reply Polska czy Etisoft Smart Solutions. Twórca kursów, z których skorzystało już ponad 11 000 osób w Strefie Kursów i ponad 1 000 kursantów na dev-hobby.pl.

Specjalizacja: Clean Code, Clean Architecture i uczenie programowania tak, żeby dało się je naprawdę zrozumieć — nie wykuć.

🚀 Co dalej?

Zobacz to w praktyce na wideo i pobierz darmową roadmapę, żeby ułożyć naukę w spójną ścieżkę do pierwszej pracy.

1 comment

Dodaj komentarz

czytanie to początek

Zamień wiedzę w umiejętności

Pobierz darmową Roadmapę .NET i ułóż takie tematy jak ten w spójną ścieżkę do pierwszej pracy.

Pobieram roadmapę →