Zastąpienie bezpośrednich zależności komunikatami

Czasami moduł musi po prostu powiadomić innych, że zaszły w nim pewne zdarzenia/zmiany i nie ma znaczenia, co stanie się z tymi informacjami później.

W tym przypadku moduły wcale nie muszą „wiedzieć o sobie”, to znaczy zawierać bezpośrednie linki i wchodzić w bezpośrednią interakcję, ale wystarczy wymieniać komunikaty (wiadomości) lub zdarzenia (zdarzenia).

Czasami wydaje się, że komunikacja modułu za pomocą komunikatorów jest znacznie słabsza niż bezpośrednia zależność. Rzeczywiście, ponieważ metody nie są wywoływane, nie ma informacji o klasach. Ale to nic więcej niż iluzja.

Zamiast nazw metod, logika zaczyna być powiązana z typami komunikatów, ich parametrami i przesyłanymi danymi. Łączność takich modułów jest rozmazana.

Kiedyś było tak: wywołujemy metody - jest łączność, nie wywołujemy metod - nie ma łączności. Teraz wyobraź sobie, że moduł A zaczął wysyłać w swoich komunikatach nieco inne dane. Jednocześnie wszystkie moduły zależne od tych komunikatów nie będą działać poprawnie.

Załóżmy, że wcześniej przy dodawaniu nowego użytkownika moduł autoryzacyjny wysłał komunikat USER_ADDED, a po aktualizacji zaczął wysyłać ten komunikat przy próbie rejestracji i dodatkowo w parametrach wskazuje pomyślną rejestrację lub jej brak.

Dlatego bardzo ważne jest bardzo kompetentne wdrożenie mechanizmu komunikatów. Są do tego różne szablony.

Obserwator. Stosowany jest w przypadku zależności jeden-do-wielu, gdy wiele modułów jest zależnych od stanu jednego - głównego. Wykorzystuje mechanizm mailingu, co oznacza, że ​​moduł główny po prostu wysyła te same wiadomości do wszystkich swoich subskrybentów, a moduły zainteresowane tymi informacjami implementują interfejs „subskrybent” i zapisują się na listę mailingową.

Takie podejście jest szeroko stosowane w systemach z interfejsem użytkownika, pozwalając rdzeniu aplikacji (modelowi) pozostać niezależnym, jednocześnie informując powiązane z nim interfejsy, że coś się zmieniło i wymaga aktualizacji.

Tutaj format wiadomości jest ustandaryzowany na poziomie systemu operacyjnego, którego twórcy muszą zadbać o kompatybilność wsteczną i dobrą dokumentację.

Organizacja interakcji poprzez dystrybucję wiadomości ma dodatkowy „bonus” - opcjonalne istnienie „subskrybentów” „publikowanych” (czyli wysyłanych) wiadomości. Dobrze zaprojektowany system, taki jak ten, umożliwia dodawanie/usuwanie modułów w dowolnym momencie.

Magistrala wiadomości

Możesz zorganizować wymianę wiadomości i użyć do tego wzorca Mediator w inny sposób .

Jest używany, gdy istnieje zależność wiele-do-wielu między modułami. Mediator pełni rolę pośrednika w komunikacji pomiędzy modułami, działając jako centrum komunikacyjne i eliminując konieczność wyraźnego odnoszenia się modułów do siebie.

W rezultacie interakcja modułów między sobą („wszyscy ze wszystkimi”) zostaje zastąpiona interakcją modułów tylko z pośrednikiem („jeden ze wszystkimi”). Mówi się, że mediator obejmuje interakcję między wieloma modułami.

Magistrala wiadomości

Jest to tak zwany inteligentny pośrednik . To tam programiści najczęściej zaczynają dodawać swoje kule, które wpływają na zachowanie poszczególnych modułów poprzez włączanie/wyłączanie otrzymywania określonych komunikatów.

Typowym przykładem z życia wziętym jest kontrola ruchu na lotniskach. Wszystkie komunikaty z samolotów trafiają do wieży kontrolnej kontrolera, zamiast być przesyłane bezpośrednio między samolotami. A kontroler już podejmuje decyzje o tym, które samoloty mogą startować lub lądować, a z kolei wysyła wiadomości do samolotów.

Ważny! Moduły mogą wysyłać sobie nawzajem nie tylko proste komunikaty, ale także obiekty poleceń. Taka interakcja jest opisana w szablonie polecenia . Najważniejsze jest hermetyzacja żądania wykonania określonej akcji jako osobnego obiektu.

W rzeczywistości ten obiekt zawiera pojedynczą metodę execute() , która następnie umożliwia przekazanie tej akcji innym modułom do wykonania jako parametr i generalnie wykonywanie dowolnych operacji na obiekcie polecenia, które można wykonać na zwykłych obiektach.

Prawo Demeter

Prawo Demeter zabrania używania ukrytych zależności: „Obiekt A nie może mieć bezpośredniego dostępu do obiektu C, jeśli obiekt A ma dostęp do obiektu B, a obiekt B ma dostęp do obiektu C”.

Oznacza to, że wszystkie zależności w kodzie muszą być „jawne” – klasy/moduły mogą w swojej pracy wykorzystywać tylko „swoje zależności” i nie powinny przechodzić przez nie do innych. Dobrym przykładem jest architektura trójwarstwowa. Warstwa interfejsu powinna współpracować z warstwą logiczną, ale nie powinna oddziaływać bezpośrednio z warstwą bazy danych.

W skrócie, ta zasada jest również sformułowana w ten sposób: „Wchodź w interakcje tylko z najbliższymi przyjaciółmi, a nie z przyjaciółmi przyjaciół”. Osiąga to mniejszą spójność kodu, a także większą widoczność i przejrzystość jego projektu.

Prawo Demeter realizuje wspomnianą już „zasadę minimalnej wiedzy”, która jest podstawą luźnego sprzężenia i polega na tym, że obiekt/moduł powinien znać jak najmniej szczegółów o budowie i właściwościach innych obiektów/modułów oraz cokolwiek ogólnie, w tym własne komponenty .

Analogia z życia: jeśli chcesz, żeby pies biegał, głupio jest wydawać komendy łapom, lepiej wydać komendę psu, a ona sama sobie z łapami poradzi.

Skład zamiast dziedziczenia

To bardzo obszerny i ciekawy temat i zasługuje przynajmniej na osobny wykład. Wiele kopii zostało zerwanych na ten temat w Internecie, dopóki nie osiągnięto konsensusu - używamy dziedziczenia do minimum, kompozycji - do maksimum.

Chodzi o to, że dziedziczenie faktycznie zapewnia najsilniejszy związek między klasami, więc należy go unikać. Ten temat jest dobrze omówiony w artykule Herba Suttera „ Preferuj kompozycję zamiast dziedziczenia ”.

Kiedy zaczniesz uczyć się wzorców projektowych, natkniesz się na całą masę wzorców rządzących tworzeniem obiektu lub jego wewnętrznej struktury. Przy okazji mogę doradzić w tym kontekście zwrócenie uwagi na wzorce Delegate/Delegate oraz Component , które wywodzą się z gier .

O wzorach porozmawiamy nieco później.