Wzorzec projektowy mediator - okładka

Wzorzec projektowy Mediator

Opublikowano Kategorie Czysty kodCzas czytania 6min

Przez długi czas wzorzec projektowy Mediator kojarzył mi się z bardzo skomplikowanym mechanizmem, który jest dedykowany dużym systemom i którego poznania trochę unikałem. Okazuje się, że strach ma wielkie oczy, a Mediatora można sprowadzić do dość prostego komponentu, który z powodzeniem można stosować nawet w małych aplikacjach.

Ten wpis jest kolejnym wpisem z serii o wzorcach projektowych. Jeśli chcesz poznać inne wzorce projektowe lub dowiedzieć się czym są wzorce, to koniecznie sprawdź mój wpis o wzorcach projektowych.

Założenia wzorca projektowego Mediator

Mediator jest jednym z behawioralnych wzorców projektowych. Chcąc zrozumieć założenia i działanie wzorca Mediator można przedstawić na przykładzie instytucji mediatora w procesie mediacji sądowych. Zadaniem mediatora w sądzie jest pośredniczenie w komunikacji między dwoma lub więcej podmiotami. Identycznie sytuacja wygląda w przypadku wzorca projektowego. Zadaniem mediatora jest pośredniczenie w komunikacji między obiektami w systemie. Jednocześnie, wykorzystanie mediatora ogranicza bezpośrednią komunikację między obiektami i zmusza je do komunikacji za pośrednictwem dedykowanego komponentu.

Zwykle, klasa będąca mediatorem wystawia metodę, która służy do komunikacji między obiektami. Zadaniem mediatora jest odebranie wiadomości od jednego obiektu i przekazanie jej do innego. Na diagramie poniżej znajdziesz przykład aplikacji, w której wykorzystany został wzorzec mediator do komunikacji w grupie znajomych.

Wzorzec projektowy mediator - diagram

W klasie Mediator zaimplementowano metodę send, która przyjmuje wiadomość od użytkownika, a następnie przekazuje ją do drugiego. Każdy użytkownik dysponuje metodą receive do otrzymywania wiadomości.

Spotkałem się również z podejściem z dwiema metodami. Mogą to być np. metody commandquery. Głównym założeniem było wystawienie dedykowanych metod dla wysyłania zapytań i poleceń w systemie. Celem poleceń było wykonywanie akcji w systemie. Z kolei celem zapytań było zwracanie wyników. Takie podejście do tematu może stanowić dobry wstęp do implementacji wzorca CQRS w budowanym systemie. Oczywiście nic nie stoi na przeszkodzie, by zarówno zapytania, jak i polecenia były obsługiwane przez mediatora za pomocą jednej metody.

Mediator – wady i zalety

Wykorzystanie Mediatora niesie ze sobą kilka korzyści. Po pierwsze, osobny komponent dedykowany komunikacji pomaga pisać kod zgodny z zasadą Single Responsibility Principle. Drugą istotną zaletą jest redukcja couplingu między klasami. Obiekty, które się komunikują ze sobą, nie muszą o sobie nic wiedzieć. Mała liczba zależności pozwala również w łatwy sposób rozszerzać system o nowe obiekty. Co więcej, testowanie takiego systemu powoduje mniej komplikacji. W końcu scentralizowany komponent odpowiedzialny za komunikację sprawia, że proces debuggowania jest potencjalnie prostszy. Z góry znana jest ścieżka wywołań metod i przepływu danych w aplikacji, co może przyspieszyć detekcję wadliwego fragmentu kodu.

Scentralizowany komponent jest jednak potencjalnym zagrożeniem. Mediator stanowi potencjalny Single Point of Failure, a błąd w nim może wpłynąć na całą aplikację. Mimo że coupling między obiektami zostaje zmniejszony, to powstaje coupling między obiektami a Mediatorem. Mediator staje się głównym obiektem w systemie i wiele innych obiektów od niego zależy, przez co wdrażanie zmian w samym obiekcie Mediatora może być problematyczne.

W ostatecznym rozrachunku uważam, że jeśli w aplikacji wykorzystanie Mediatora ma sens, to gra jest warta świeczki. Moim zdaniem, przedstawione problemy są do zaakceptowania, patrząc na zysk, jaki można uzyskać, wykorzystując ten wzorzec projektowy.

Wzorzec projektowy Mediator w praktyce

Do przedstawienia działania Mediatora w kodzie zaimplementuję komponenty obecne na przedstawionym wcześniej diagramie. Do implementacji wykorzystałem TypeScript, ale dołożyłem wszelkich starań, by przykłady były zrozumiałe i możliwe do implementacji w większości popularnych języków programowania.



class Message {
  public readonly text: string;
  public readonly recepient: string;

  public constructor( { text, recepient }: { recepient: string; text: string; } ) {
    this.recepient = recepient;
    this.text = text;
  }
}

class Person {
  public constructor( public readonly name: string ) {}

  public receive( message: Message ) {
    console.log( 'Thanks!', message );
  }
}

class Mediator {
  public constructor( private readonly _people: Person[] = [] ) {}

  public send( message: Message ) {
    const recepient = this._people.find(
      person => message.recepient === person.name
    );

    if ( !recepient ) { 
      throw new Error( 'Recepient not found!' );
    }

    recepient.receive( message );
  }
}

const john = new Person( 'John' );
const bob = new Person( 'Bob' );
const tom = new Person( 'Tom' );

const mediator = new Mediator( [ john, bob, tom ] );

mediator.send( new Message( { text: 'Hello!', recepient: 'Bob' } ) );
mediator.send( new Message( { text: 'Have a good day!', recepient: 'Tom' } ) );

Rezultatem uruchomienia powyższego kodu będzie wylogowanie dwóch logów w konsoli z treścią wysłanych wiadomości. Tak zaprojektowany mediator pozwala na komunikację zewnętrznych obiektów z użytkownikami za pośrednictwem mediatora. Nieco przypomina to wykorzystanie wzorca Proxy. Taka konstrukcja miałaby sens w systemie wysyłającym np. notyfikacje poszczególnym osobom.

W przedstawionym przykładzie więcej sensu miałaby implementacja mechanizmu umożliwiającego komunikację między użytkownikami. Aby ją zaimplementować, wystarczy nieco zmodyfikować wcześniej przedstawiony przykład.

Wzorzec projektowy mediator - zmodyfikowany diagram
Implementacja komunikacji dwustronnej między osobami w kodzie wygląda następująco.


class Message {
  public readonly text: string;
  public readonly recepient: string;

  public constructor( { text, recepient }: { recepient: string; text: string; } ) {
    this.recepient = recepient;
    this.text = text;
  }
}

class Person {
  public constructor(
    private readonly _mediator: Mediator,
    public readonly name: string
  ) {}

  public receive( message: Message ) {
    console.log( 'Thanks!', message );
  }

  public send( message: Message ) {
    this._mediator.send( message );
  }
}

class Mediator {
  private readonly _people: Person[] = []

  public constructor() {}

  public send( message: Message ) {
    const recepient = this._people.find(
      person => message.recepient === person.name
    );

    if ( !recepient ) { 
      throw new Error( 'Recepient not found!' );
    }

    recepient.receive( message );
  }

  public addPerson( person: Person ) {
    this._people.push( person );
  }
}

const mediator = new Mediator();

const john = new Person( mediator, 'John' );
const bob = new Person( mediator, 'Bob' );
const tom = new Person( mediator, 'Tom' );

mediator.addPerson( john );
mediator.addPerson( bob );
mediator.addPerson( tom );

john.send( new Message( { text: 'Hello!', recepient: 'Bob' } ) );
bob.send( new Message( { text: 'Hi, have a good day!', recepient: 'John' } ) );

Podsumowanie

Jeśli w Twoim systemie istnieje pajęczyna zależności między obiektami, to Mediator może być idealnym kandydatem do rozwiązania tego problemu przy najbliższym refaktorze. Daj znać w komentarzu, czy miałeś/aś okazję korzystać już z tego wzorca projektowego oraz jakie korzyści Ci dał. Zachęcam do sprawdzenia artykułów podlinkowanych poniżej. Wiedza w nich zawarta istotnie pozwoliła mi zrozumieć założenia Mediatora i zastosować go w praktyce.

Źródła i materiały dodatkowe

Dominik Szczepaniak

Zawodowo Senior Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchni, miłośnik jazdy na rowerze i treningu siłowego.

Inne wpisy, które mogą Cię zainteresować

Zapisz się na mailing i odbierz e-booka

Zapisując się na mój mailing, otrzymasz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.

Subscribe
Powiadom o
guest

0 komentarzy
Inline Feedbacks
View all comments