Mediator design pattern jest przykładem behawioralnego (czynnościowego) wzorca projektowego. Wzorzec ten zastosujemy w przypadku gdy w naszym systemie będziemy posiadali wiele komunikujących się ze sobą obiektów. Wraz z rozbudową systemu i rosnącą liczbą obiektów powiązania między nimi mogą zacząć być trudne do utrzymania. Do tego każda zmiana w interfejsach obiektów będzie pociągała za sobą zmiany w sposobie komunikacji. W takim przypadku dobrym rozwiązaniem może być zrezygnowanie z bezpośredniej komunikacji na rzecz jednego centralnego węzła komunikacyjnego – mediatora.
Czym jest mediator?
Mediator najczęściej jest zwykłym obiektem używanym jako centralny węzeł komunikacyjny pomiędzy rozproszonymi częściami naszego systemu. Można w uproszczeniu powiedzieć, iż „mediator wie o wszystkich a wszyscy wiedzą tylko o mediatorze„. Poszczególne obiekty są świadome tego jak mają komunikować się z mediatorem (znają jego interfejs). Nie wiedzą za to nic o innych obiektach w systemie. To mediator dokładnie wie jak obsłużyć routing i komunikację zdejmując tą odpowiedzialność z innych obiektów. Jedynie on musi znać interfejsy innych części aplikacji. Takie podejście do problemu może sprawić, iż nasz kod i zawarta w nim logika będzie dużo czytelniejsza, ale…
Posiadanie mediatora wiąże się z jednym dość istotnym problemem. Jeżeli mediator przestanie działać – przestanie działać także wszystko co z niego korzysta. Dlatego więc należy zawsze przyłożyć dużą wagę na odpowiednie testowanie oraz obsługę błędów w mediatorze.
Przykład
Myśląc o przykładzie systemu w którym mógłby wystąpić mediator i zarządzać komunikacją między obiektami pewnie wielu osobom do głowy przyjdą komunikatory (np. Slack, MS Teams, etc.). W praktyce w takiej aplikacji nie będziemy wysyłali wiadomości bezpośrednio do innych użytkowników. Wiadomość taka trafi do odpowiedniego mechanizmu (mediatora), który to dopiero będzie wiedział jak ją pokierować, aby trafiła do właściwego okna. Mam nadzieję, że poniższy przykład wraz z komentarzami pomoże w zrozumieniu idei wzorca mediatora.
// pojedyńczy użytkownik czatu
// w momencie inicjalizowania nie wie on nic o innych użytkownikach
class User {
constructor(nick) {
this.nick = nick; // identyfikuje go jedynie nick
this.chatroom = null; // i może on dołączyć do jednego pokoju czatu
}
addToChatroom(chatroom) {
// za pomocą tej metody może dołączyć do czatu
this.chatroom = chatroom;
}
sendMessage(message, to) {
// może wysłać wiadomość - trafi ona do mediatora
// przypominajka - this będzie odnosiło się do konkretnej instancji Usera
this.chatroom.send(message, this, to);
}
receive(message, from, type) {
// może wyświetlić otrzymaną wiadomość oraz jej nadawcę
// wiadomość otrzymamy od mediatora
console.log(
`[To ${this.nick}] New ${type} message from ${from.nick}: ${message}`
);
}
}
// "Chatroom" to mediator w naszym systemie
// o on będzie odbierał i wysyłał dalej wiadomości
class Chatroom {
constructor() {
this.participants = {}; // mediator wie o wszystkich uczestnikach czatu
}
join(participant) {
// dodajemy nowego użytkownika do listy wszystkuch użytkowników
this.participants[participant.nick] = participant;
// łączymy użytkownika z tym konkretnym mediatorem
// dzięki temu użytkownik będzie mógł z niego korzystać
participant.addToChatroom(this);
}
/*
Mediator jest odpowiedzilany za prawidłowe rozsyłanie dalej
wiadomości które otrzyma od użytkowników.
Możemy dodać tutaj wiele funkcjonalności jak np.
filtrowanie niecenzuralnych słów, szyfrowanie/deszyfrowanie, itd.
*/
send(message, from, to) {
if (to) {
// jeżeli sprecyzujemy odbiorcę,
// mediator wyśle to jako wiadomość prywatną
to.receive(message, from, "private");
} else {
// w przeciwnym wypadku wiadomośc trafi na czat grupowy
// tzn. zostanie wysłana do wszystkich
for (const key in this.participants) {
if (this.participants[key] !== from) {
this.participants[key].receive(message, from, "public");
}
}
}
}
}
// Tworzymy instancję naszego mediatora
const chatroom = new Chatroom();
// Tworzymy uczestników czatu
const jan = new User("Jan");
const kamil = new User("Kamil");
const aga = new User("Aga");
const pjtr = new User("pjtr");
// użytkownicy czatu dołączają do pokoju czatu
chatroom.join(jan);
chatroom.join(kamil);
chatroom.join(aga);
chatroom.join(pjtr);
// i wysyłają na czacie wiadomości
jan.sendMessage("Czy znacie jakieś fajne filmy?");
jan.sendMessage("Szukam czegoś na weekend"); // wiadomość grupowa
kamil.sendMessage("Hej, prywatnie mogę Ci polecić La La Land", jan);
aga.sendMessage("Sprzedam Opla!");
pjtr.sendMessage("Aga, chyba nie ten kanał...", aga); // wiadomość prywatna
Podsumowanie
Mediator design pattern nie jest może jakimś często używanym wzorcem projektowym w JavaScript. Jednak znajomość tego podejścia może niejednokrotnie pomóc nam rozwiązać problemy komunikacji pojawiające się w rozproszonych systemach.
Masz uwagi lub sugestie do tego wpisu?
Przejdź na Discord