GRASP

Zasady projektowe

  |   5 min czytania

Wstęp

GRASP (General Responsibility Assignment Software Patterns) jest zbiorem dziewięciu zasad określających zakres odpowiedzialności klas i obiektów w systemie. Ułatwia zrozumienie struktury i sposobu działania tworzonego projektu obiektowego oraz ogranicza wpływ zmian. Nazwy zasad są umowne i nie są powszechnie używane, a ich realizacja może wynikać ze znanych wzorców obiektowych. Co więcej można powiedzieć, że wzorce projektowe Gang of Four są po części konsekwencją zaaplikowania zasad GRASP.

Information Expert

Podstawowym zadaniem w trakcie tworzenia każdego projektu jest decyzja o przypisaniu danej odpowiedzialności do wybranej klasy.

Problem
Wybór klasy realizującej wybraną odpowiedzialność.

Rozwiązanie
Odpowiedzialność powinna zostać oddelegowana do klasy posiadającej wszystkie niezbędne informacje wymagane do realizacji zadania uwzględniając wielkość grafu przejść (zależności).

Creator

Właściwe przypisanie odpowiedzialności w zakresie tworzenia obiektów sprawia, że projekt jest czytelniejszy i łatwiej rozwijalny z uwagi na zmniejszenie ilości powiązań między obiektami. Jeśli istnieje kilku kandydatów twórców wówczas może powstać problem decyzyjny. Wyjątkiem od tej reguły może być np. wzorzec Fabryka.

Problem
Wybór klasy B odpowiedzialnej za tworzenie innego obiektu klasy A.

Rozwiązanie
Klasa B powinna mieć przypisaną odpowiedzialność tworzenia obiektu klasy A, gdy B agreguje obiekty klasy A, B rejestruje cykl życia instancji klasy A, B blisko współpracuje z A lub B posiada zbiór informacji inicjalizacyjnych wymaganych przy tworzeniu A.

Controller

Interfejs użytkownika jest odpowiedzialny za interakcję z użytkownikiem poprzez przechwytywanie informacji wejściowych i wyświetlanie stanu. Jednakże z uwagi na oddzielenie widoku od logiki aplikacji część funkcji systemu powinna zostać realizowana przez obiekt kontrolera nie należący do interfejsu użytkownika. Pozwala to prowadzenie architektury aplikacji co jest aplikowane m.in. we wzorcach MVP i MVVM.

Problem
Wybór obiektu nie należącego do interfejsu użytkownika, który powinien obsłużyć operację systemu.

Rozwiązanie
Delegacja odpowiedzialności do klasy, która reprezentuje cały system jako całość (główny obiekt kontrolera, podsystem) lub reprezentuje przypadek użycia systemu z wystąpieniem danej operacji.

Low Coupling

Klasa o dużej ilości powiązań jest silnie zależna od innych klas co może skutkować brakiem możliwości ponownego użycia, utrudnić izolacje oraz wymusić wiele lokalnych zmian wynikających ze zmian klas powiązanych. O powiązaniu klasy A i B można mówić, gdy A ma atrybut typu B lub C związanego z B, A wywołuje metody B, A posiada metodę związaną z B lub A dziedziczy po B.

Problem
Utrzymanie niskiej ilości powiązań między obiektami umożliwiającej redukcje wpływu zmian na projekt oraz zwiększenie możliwości ponownego wykorzystania obiektów.

Rozwiązanie
W trakcie przypisywania odpowiedzialności do obiektów należy dążyć do minimalizacji ilości powiązań (w przypadku wyboru alternatywnego).

High Cohesion

Klasa powinna być nastawiona na realizację pojedynczej odpowiedzialności, aby uniknąć tworzenie super klasy i zachować wysoką spójność. Część zadań może zostać oddelegowana do innych klas. Szczególnie nie należy łączyć odpowiedzialności różnych warstw aplikacji. Reguła ta zawiera się w zasadzie Single Responsibility Principle (SOLID).

Problem Utrzymanie klasy o pojedynczej odpowiedzialności ułatwiającej modyfikacje kodu.

Rozwiązanie
Przypisywanie odpowiedzialności powinno dążyć do maksymalizacji spójności klasy (w przypadku wyboru alternatywnego).

Polymorphism

Wykorzystanie mechanizmu polimorfizmu jest nieodłącznym elementem tworzenia projektów w programowaniu obiektowym. Pozwala na zachowanie odpowiedniej struktury i hierarchii klas oraz wykorzystanie elementów na kilka różnych sposobów niezależnie od ich właściwych typów. Umożliwia eliminacje wielu instrukcji warunkowych podejmujących decyzję w zależności od konkretnego typu.

Problem Obsługa zadań i odpowiedzialności w zależności od typów obiektów.

Rozwiązanie
Przydział zobowiazań za pomocą polimorfizmu w miejscu gdzie występuje różnica w zachowaniu obiektów.

Pure Fabrication

Niektóre zasady GRASP w pewnych przypadkach mogą stać ze sobą w sprzeczności (High Cohesion i Low Coupling) lub wskazywany wariant jest nie do zaakceptowania (Informator Expert). Wówczas powstaje problem decyzyjny o obarczeniu obiektu odpowiedzialnością łamiąc jak najmniejszą ilość zasad znajdując złoty środek. Przykładem takiego bytu może być wzorzec Singleton.

Problem Wybór obiektu do realizacji wybranego zadania przy jednoczesnym zachowaniu wzajemnie wykluczających się reguł.

Rozwiązanie Stworzenie nowej klasy pomocniczej nie reprezentującej żadnego problemu domenowego i przypisanie jej zbioru powiązanych wymagań.

Indirection

W procesie tworzenia projektu należy dążyć do minimalizacji powiązań obiektów. W szczególności może zachodzić potrzeba eliminacji bezpośrednich zależności między obiektami bez wpływu na sposób działania. W tym celu wymagany jest obiekt pośredni umożliwiający współpracę różnych elementów bez wiedzy o sobie. Przykładem realizacji zasady może być wzorzec Adapter i Fasada.

Problem Przydział zobowiązania w sytuacji, gdy oczekiwane jest uniknięcie bezpośredniego powiązania między obiektami.

Rozwiązanie
Przypisanie odpowiedzialności do nowego pośredniego obiektu, którego rolą jest dostarczenie komunikacji dla elementów systemu w taki sposób, aby nie były one zależne bezpośrednio od siebie.

Protected Variations

W trakcie procesu projektowania i implementacji systemu należy zadbać o możliwie jak najprostszą ewentualną wymiane elementów systemu na alternatywne. Pozwala to na łatwe dodawanie nowych rozszerzeń i niższy koszt wprowadzanych zmian, modyfikacje implementacji bez oddziaływania na klientów oraz redukcję zależności. Reguła ta zawiera się w zasadzie Open-Closed Principle (SOLID).

Problem Projektowanie obiektów w taki sposób, aby ich modyfikacja nie wywierała szkodliwego wpływu na inne elementy projektu.

Rozwiązanie
Identyfikacja punktów przewidywanych różnic i niestabilności oraz przypisanie odpowiedzialności do wspólnego stabilnego interfejsu odwołującego się do konkretnych implementacji.