Dekompozycja hierarchiczna
Nigdy nie należy od razu rozpoczynać pisania klas dla swojej aplikacji. Najpierw trzeba go zaprojektować. Projekt powinien kończyć się przemyślaną architekturą. Aby uzyskać tę architekturę, musisz konsekwentnie dekomponować system.
Dekompozycję należy przeprowadzić hierarchicznie – najpierw system dzieli się na duże funkcjonalne moduły/podsystemy, które opisują jego działanie w najbardziej ogólnej formie. Następnie powstałe moduły są analizowane bardziej szczegółowo i dzielone na podmoduły lub obiekty.
Przed wybraniem obiektów podziel system na podstawowe bloki semantyczne, przynajmniej mentalnie. W małych aplikacjach jest to zwykle bardzo łatwe: wystarczy kilka poziomów hierarchii, ponieważ system jest najpierw podzielony na podsystemy/pakiety, a pakiety na klasy.
![Dekompozycja hierarchiczna](https://cdn.codegym.cc/images/article/d5e9aa2b-5783-430f-b5b5-1832bc33c540/800.jpeg)
Ten pomysł nie jest tak trywialny, jak się wydaje. Na przykład, jaka jest istota tak powszechnego „wzorca architektonicznego”, jak Model-View-Controller (MVC)?
Chodzi o oddzielenie prezentacji od logiki biznesowej . Po pierwsze, każda aplikacja użytkownika jest podzielona na dwa moduły – jeden odpowiada za implementację samej logiki biznesowej (Model), a drugi odpowiada za interakcję z użytkownikiem (Interfejs użytkownika lub Widok).
Wtedy okazuje się, że moduły muszą jakoś współdziałać, w tym celu dodają Kontrolera, którego zadaniem jest zarządzanie interakcją modułów. Również w mobilnej (klasycznej) wersji MVC dodany jest do niego wzorzec Observer, dzięki czemu Widok może odbierać zdarzenia z modelu i zmieniać wyświetlane dane w czasie rzeczywistym.
Typowe moduły najwyższego poziomu, uzyskiwane w wyniku pierwszego podziału systemu na największe komponenty, to właśnie:
- Logika biznesowa;
- Interfejs użytkownika;
- Baza danych;
- System przesyłania wiadomości;
- Kontener obiektów.
Pierwszy podział zwykle dzieli całą aplikację na 2-7 (maksymalnie 10 części). Jeśli rozłożymy to na więcej części, to pojawi się chęć ich pogrupowania i znowu otrzymamy 2-7 modułów najwyższego poziomu.
Dekompozycja funkcjonalna
Podziału na moduły/podsystemy najlepiej dokonać na podstawie zadań, które system rozwiązuje . Zadanie główne jest podzielone na składowe podzadania, które mogą być rozwiązywane/wykonywane autonomicznie, niezależnie od siebie.
Każdy moduł powinien być odpowiedzialny za rozwiązanie jakiegoś podzadania i pełnienie odpowiadającej mu funkcji . Oprócz celu funkcjonalnego, moduł charakteryzuje się również zestawem danych niezbędnych do realizacji jego funkcji, czyli:
Moduł = Funkcja + Dane potrzebne do jej wykonania.
Jeśli rozkład na moduły zostanie wykonany poprawnie, to interakcja z innymi modułami (odpowiedzialnymi za inne funkcje) będzie minimalna. Może tak być, ale jego brak nie powinien być krytyczny dla twojego modułu.
Moduł nie jest dowolnym fragmentem kodu, ale oddzielną funkcjonalnie znaczącą i kompletną jednostką programu (podprogramem), która zapewnia rozwiązanie określonego zadania i, w idealnym przypadku, może działać niezależnie lub w innym środowisku i być ponownie wykorzystywana. Moduł powinien być rodzajem „integralności zdolnej do względnej niezależności w zachowaniu i rozwoju”. (Krzysztof Aleksander)
Właściwa dekompozycja opiera się więc przede wszystkim na analizie funkcji systemu i danych niezbędnych do realizacji tych funkcji. Funkcje w tym przypadku nie są funkcjami i modułami klasy, ponieważ nie są obiektami. Jeśli masz tylko kilka zajęć w module, to przesadziłeś.
Mocna i słaba łączność
Bardzo ważne jest, aby nie przesadzić z modularyzacją. Jeśli dasz początkującemu monolityczną aplikację Springa i poprosisz go o podzielenie jej na moduły, to wyjmie każdego Spring Beana do osobnego modułu i uzna, że jego praca jest zakończona. Ale nie jest.
Głównym kryterium jakości dekompozycji jest to, w jaki sposób moduły są skoncentrowane na rozwiązywaniu swoich zadań i są niezależne.
Zwykle formułuje się to w następujący sposób: „Moduły otrzymane w wyniku dekompozycji powinny być maksymalnie skoniugowane wewnętrznie (wysoka spójność wewnętrzna) i minimalnie połączone ze sobą (niskie sprzężenie zewnętrzne)”.
Wysoka spójność, wysoka spójność lub „spójność” w module wskazuje, że moduł jest skoncentrowany na rozwiązaniu jednego wąskiego problemu i nie jest zaangażowany w wykonywanie heterogenicznych funkcji lub niezwiązanych ze sobą obowiązków.
Spójność charakteryzuje stopień, w jakim zadania realizowane przez moduł są ze sobą powiązane.
Konsekwencją High Cohesion jest Single Responsibility Principle – pierwsza z pięciu zasad SOLID , zgodnie z którą każdy obiekt/moduł powinien mieć tylko jedną odpowiedzialność i nie powinien istnieć więcej niż jeden powód do jego zmiany.
Low Coupling , czyli luźne powiązanie, oznacza, że moduły, na które podzielony jest system, powinny być w miarę możliwości niezależne lub luźno ze sobą połączone. Powinni umieć wchodzić w interakcje, ale jednocześnie wiedzieć o sobie jak najmniej.
Każdy moduł nie musi wiedzieć, jak działa drugi moduł, w jakim języku jest napisany i jak działa. Często do zorganizowania interakcji takich modułów używany jest pewien kontener, do którego te moduły są ładowane.
Przy odpowiednim projekcie, jeśli zmienisz jeden moduł, nie będziesz musiał edytować innych lub zmiany te będą minimalne. Im luźniejsze sprzężenie, tym łatwiej napisać/zrozumieć/rozszerzyć/naprawić program.
Uważa się, że dobrze zaprojektowane moduły powinny charakteryzować się następującymi właściwościami:
- Integralność i kompletność funkcjonalna - każdy moduł realizuje jedną funkcję, ale realizuje ją dobrze i kompletnie, moduł samodzielnie wykonuje pełen zestaw operacji w celu realizacji swojej funkcji.
- Jedno wejście i jedno wyjście - na wejściu moduł programu otrzymuje określony zestaw danych początkowych, wykonuje znaczące przetwarzanie i zwraca jeden zestaw danych wynikowych, czyli realizowana jest standardowa zasada IPO - wejście -\u003e proces -\u003e wyjście.
- Niezależność logiczna - wynik pracy modułu programu zależy tylko od danych początkowych, ale nie zależy od pracy innych modułów.
- Słabe powiązania informacyjne z innymi modułami – należy w miarę możliwości ograniczyć do minimum wymianę informacji pomiędzy modułami.
Początkującemu bardzo trudno jest zrozumieć, jak jeszcze bardziej zmniejszyć łączność modułów. Po części ta wiedza przychodzi z doświadczeniem, po części - po przeczytaniu mądrych książek. Najlepiej jednak przeanalizować architektury istniejących aplikacji.
Skład zamiast dziedziczenia
Właściwa dekompozycja to dla większości programistów swego rodzaju sztuka i trudne zadanie. Prostota jest tutaj zwodnicza, a błędy są kosztowne.
![](https://cdn.codegym.cc/images/article/d7e785ed-e16d-4354-883c-0ad83327512a/512.jpeg)
Zdarza się, że dedykowane moduły są ze sobą mocno powiązane i nie mogą być rozwijane niezależnie. Lub nie jest jasne, za jaką funkcję odpowiada każdy z nich. Jeśli napotkasz podobny problem, najprawdopodobniej partycjonowanie na moduły zostało wykonane nieprawidłowo.
Zawsze powinno być jasne, jaką rolę odgrywa każdy moduł . Najbardziej niezawodnym kryterium prawidłowego przeprowadzenia dekompozycji jest to, czy moduły są niezależnymi i wartościowymi podprogramami, których można używać w oderwaniu od reszty aplikacji (a zatem można ich ponownie użyć).
Dekomponując system, warto sprawdzić jego jakość, zadając sobie pytania: „Jakie zadanie wykonuje każdy moduł?”, „Jak łatwo testować moduły?”, „Czy można używać modułów samodzielnie czy w innym środowisku?” wpływają na innych?”
Musisz starać się, aby moduły były jak najbardziej autonomiczne . Jak wspomniano wcześniej, jest to kluczowy parametr dla prawidłowego rozkładu . Dlatego należy to przeprowadzić w taki sposób, aby moduły początkowo były od siebie słabo zależne. Jeśli ci się udało, to jesteś wielki.
Jeśli nie, to i tutaj nie wszystko stracone. Istnieje szereg specjalnych technik i wzorców, które pozwalają jeszcze bardziej zminimalizować i osłabić powiązania między podsystemami. Przykładowo w przypadku MVC wykorzystano do tego celu wzorzec Observer, ale możliwe są inne rozwiązania.
Można powiedzieć, że techniki odsprzęgania stanowią główny „zestaw narzędzi architekta”. Trzeba tylko zrozumieć, że mówimy o wszystkich podsystemach i konieczne jest osłabienie połączenia na wszystkich poziomach hierarchii , czyli nie tylko między klasami, ale także między modułami na każdym poziomie hierarchii.