Wzorzec Mediator w JavaScripcie.

Jakiś czas temu opisałem jak w JavaScripcie stworzyć prostą implementację wzorca Obserwatora. Dziś pokażę trochę podobny wzorzec, mediator.

Tak jak obserwator, mediator pomaga zorganizować połączenia między obiektami. Jednak w przeciwieństwie do obserwatora, który tworzy wśród obiektów relacje „jeden do wielu”, mediator tworzy mechanizm pozwalający obiektom komunikować się na zasadzie „wielu do wielu”.

Wzorzec Mediator w JavaScripcie

Kiedy zaczynamy pisać aplikację i dodawać do niej kolejne elementy reprezentowane przez różne obiekty, często są one w jakiś sposób ze sobą powiązane. W zależności od stanu jednego obiektu, zmienia się zachowanie innego itp. Na początku nie sprawia to problemu, ale z czasem, gdy aplikacja się rozrasta, utrzymywanie wszystkich bezpośrednich połączeń zaczyna robić się kłopotliwe.

Ogromna ilość obiektów i jeszcze większa liczba powiązań między nimi, jest prawie niemożliwa do opanowania. W końcu wszystko zaczyna się sypać, nawet przy najmniejszej zmianie. Większość z nas to przechodziła, ja wiele razy. Nie raz na oczach milionów widzów tego bloga (np. mój projekt na daj się poznać 😉 ).

W takiej sytuacji z pomocą przychodzi technika oddzielania od siebie obiektów, po angielsku decoupling. Polega ona na wprowadzaniu do kodu mechanizmów, które zastępują bezpośrednie powiązania pomiędzy obiektami. Przykładem jest omówiony już przeze mnie obserwator, zamiast być powiązane między sobą, obserwujące obiekty, ‚podczepiają’ się tylko do obserwatora.

Mediator to kolejne rozwiązanie, które pomaga w decouplingu.

Jak działa mediator?

Dzięki mediatorowi, obiekty znajdujące się w programie mogą się ze sobą komunikować. Przy jego pomocy obiekty te (zwane też kolegami, od angielskiego colleague), mogą oddziaływać na siebie, bez jakiegokolwiek połączenia między sobą. Odwołują się jedynie do samego mediatora. Jest to bardzo wygodne i dużo łatwiejsze w utrzymaniu.

W swej najprostszej postaci, mediator udostępnia dwie metody: subscribe oraz publish. Są one dodawane do zainteresowanych obiektów, niejako w formie interfejsu.

Pierwsza z metod, subscribe, służy do zapisania danego obiektu do mediatora. W przedstawionej przeze mnie implementacji, obiekty zapisywać się będą do tematów, które je dotyczą. Jeden obiekt, może zapisać się do różnych tematów.

Druga metoda, czyli publish służy do wysyłania sygnałów na dany temat. Po takim sygnale, wszystkie obiekty, które są do tematu zapisane, zareagują.

W ten sposób obiekty w aplikacji będą wpływać na siebie nawzajem, nie mając pojęcia o swoim istnieniu. „Świadome” będą tylko istnienia mediatora. To jest właśnie decoupling w akcji. Przejdźmy może do prostej implementacji.

Mediator – implementacja w JavaScript.

Implementacja mediatora, którą tu zaprezentuje jest bardzo podstawowa, tak naprawdę to tylko schemat, który będzie można ewentualnie rozszerzyć. Wystarczy jednak aby zrozumieć zagadnienie, a to najważniejsze. Zanim przejdę do kodu samego wzorca, potrzebuję trochę kontekstu w postaci kilku obiektów:

Tworzę tu trzy proste klasy: human, dog oraz cat. Chcę aby istniały relacje między nimi. W moim zamyśle obiekty kotów i psów reagować mają na to gdy człowiek je karmi. Do tego gdy, któryś z psów zaszczeka, koty zaczną syczeć. Analogicznie, gdy kot zasyczy, wszystkie psy zaszczekają.

Nie chce jednak tworzyć bezpośrednich połączeń między obiektami. Tak jak nakazują dobre praktyki, chciałbym aby w moim kodzie miał miejsce decoupling. W tym celu użyję mediatora:

Przedstawiony tu mediator, umieszczony jest wewnątrz modułu, skonstruowanego na bazie samowywołującej się funkcji. Posiada on trzy główne elementy. Pierwszy z nich to obiekt topics. Jego pola będą reprezentowały tematy, do których zapisać się mogą inne obiekty. Kluczem pola będzie tytuł tematu, a jego zawartością, tablica z referencjami do obiektów.

Kolejny element modułu mediatora to metoda subscribe. Metoda ta będzie przypisywana do obiektów, chcących korzystać z mediatora i na nich będzie wywoływana. Przyjmuje ona dwa argumenty. Pierwszy to ciąg znaków, który posłuży jako identyfikator tematu, na który chce zapisać się dany obiekt. Drugi to metoda, która będzie wywołana, jeżeli temat ten zostanie aktywowany.

Najpierw sprawdzam, czy w obiekcie topics mediatora, nie ma już pola o takiej nazwie jak parametr. Jeżeli nie, tworzę takie pole a jego zawartość ustawiam na pustą tablicę. Następny krok to dodanie do tablicy tematu nowy obiekt, który posiada dwa pola: context oraz callback. Do pierwszego przypisuję this, co może w tej chwili wydawać się dziwne, ale za chwilę powinno stać się jasne. Pole callback jako wartość otrzymuje wskaźnik na przekazaną w drugim argumencie funkcję. To wszystko wystarczy aby zapisać obiekt do mediatora.

Ostatni element modułu to metoda publish. Służyć ona będzie do aktywowania tematów mediatora. Przyjmuje jeden argument, jest to temat, który ma być aktywowany.

Na początku sprawdzam, czy w obiekcie topics istnieje pole o kluczu równym przekazanemu argumentowi. Jeżeli nie, funkcja kończy swoje działanie. Jeżeli pole takie istnieje, iteruje przez zawartą w nim tablicę. W tablicy znajdują się obiekty, które zawierają kontekst zasubskrybowanych obiektów, oraz referencje do funkcji, które mają być wywołane w przypadku aktywacji danego tematu.

I teraz zaczyna się najciekawszy moment. Aby wywołać daną funkcję dokładnie na obiekcie, który się zasubskrybował, używam javascriptowej metody apply. wywołuje się ją po kropce na metodzie, której kontekst chcemy ustawić a jako argument wpisujemy referencję do interesującego nas kontekstu. Dobrze się składa, bo mam referencję do każdego obiektu pod ręką 🙂

Na koniec zwracam „publiczne” elementy modułu. Jest to funkcja, która przyjmuje jeden argument, obiekt. Do tego obiektu przypisywane są dwa pola, które z kolei otrzymują referencję do kolejno subscribe oraz publish.

Aby wszystko to teraz zebrać w całość, pokaże przykład użycia. Wykorzystam stworzone wyżej obiekty, tak jak to opisałem;

Najpierw dodaję wszystkim obiektom funkcjonalność mediatora. Gdy jest to gotowe, subskrybuje je na odpowiednie temty. Wszystkie zwierzęta dodaję do tematu jedzenie, same psy do tematu szczekanie a same koty do syczenie.

Teraz wystarczy że wywołam te tematy na którymś z obiektów i wszystkie zasubskrybowan, „odpowiedzą”. Tak wygląda odpowiedz z konsoli:

Prawda, że fajne? Obiekty mogą ‚komunikować się’ ze sobą, bez bezpośrednich połączeń. To jest właśnie decoupling w akcji.

Podsumowanie

Tak wygląda prosta implementacja. Oczywiście, można byłoby ją rozszerzać. Ulepszeniami, które szybko by się przydały to na przykład możliwość dodawania argumentów do callbacków, czy możliwość usuwania obiektów z listy zasubskrybowanych elementów. Jednak to co tu pokazałem na początek powinno wystarczyć.

Wzorzec mediatora może bardzo pomóc w utrzymaniu porządku wewnątrz tworzonej aplikacji, szczególnie gdy zaczyna się ona bardzo rozrastać. Nawet przy ogromnej ilości elementów, komunikacja między nimi odbywa się przez jeden wspólny punkt. Takie rozwiązanie da się bardzo łatwo skalować.

Oczywiście może to też być postrzegane jako wada. W końcu jeden punkt w aplikacji odpowiada za bardzo dużo jej elementów. Źle zaprojektowany mediator, może też zwiększyć zużycie zasobów przez program. Przyczyną tego jest to, że wywołań jest więcej niż gdyby obiekty komunikowały się „bezpośrednio” ze sobą.

Ostatecznie wszystko zależy od tego jak wygląda konkretny przypadek. Jednak oddzielanie od siebie obiektów, zawsze niesie za sobą więcej korzyści niż kłopotów.

To wszystko jeśli chodzi o implementację mediatora w JS. Jeżeli chcesz być na bieżąco z postami na blogu zachęcam do polubienia mojej strony na facebooku. Zawsze zamieszczam tam informacje o wszystkich nowościach. Jest to też dobre miejsce na kontakt ze mną. Na wszystkie pytania zawsze odpowiem :). Do przeczytania.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *