S.O.L.I.D.

S – Single Responsibility Principle

Zasada ta mówi o tym, że klasa powinna mieć jeden i tylko jeden powód do zmiany. Oznacza to że klasa powinna mieć jedną odpowiedzialność, jedną funkcjonalność i powinna robić to dobrze.

Załóżmy jesteśmy członkami klubu tenisowego. Oczywiście w czasie rejestrowania musimy podać takie informacje jak: imię, nazwisko itd. W klubie tym można również rezerwować korty tenisowe. Poniżej przedstawiona została klasa member która oprócz swoich danych zawiera także informacje związaną z rezerwacją kortu.

Ewidentnie widać, że kod jest do zmiany. Jeśli zmienią się wymagania dotyczące rezerwacji kortów tenisowych należy wtedy zmienić klasę Member.
Zgodnie z zasadą klasa powinna mieć jedną odpowiedzialność, rozdzielimy nasze zagadnienia:

Teraz jak zmienią się zasady rezerwowania kortów tenisowych, nasza klasa member zostaje bez zmian.
Ciąg dalszy naszej historii poniżej – pod literką O jak otwartość :^)

O – Open/Closed Principl

klasa powinna być otwarta na rozszerzenie ale zamknięta na modyfikację. Oznacza to tyle, że powinno się zapewnić możliwość rozszerzenia klasy, ale unikania wprowadzania zmian w klasie. Zmiana klasy może mieć wpływ na cały program, co może być ryzykowne oraz kosztowne. Zasada ta w praktyce sprowadza się do wykorzystania dziedziczenia, interfejsów, kompozycji itp.

A wiec chcemy zarezerwować kort tenisowy, więc wypadałoby za to zapłacić, ale jak? Gotówką? Kartą? A może internetowym portfelem?
Poniżej przedstawiona została sytuacja, gdzie mamy klasę Payment, która zawiera informację o rodzaju opłaty oraz klasa z porzedniego przykadu Booking.

Metoda makePayment w zależności od rodzaju opłaty zachowuje się inaczej. Przykład mimo ze wykonuje swoje zadanie ma jeden problem, jest zamknięty na rozszerzalność. Jakbyśmy chcieli dodać kolejny rodzaj opłaty np.: internetowy portfel, należało by dodać kolejny warunek a tym samym zmodyfikować klase Booking. Takie modyfikację mogę przysparzać kłopoty zwłaszcza w dużych projektach.

Aby zmienić sytuację i nie zamykać naszego programu na rozszerzenie, utworzony został interface Payment, a także nowe klasy każda odpowiada za inny rodzaj opłaty. Taki przypadek pozwala nam na rozszerzenie naszej oferty w klubie, na uwilelbiany internetowy portfel :), bez dużej modyfikacji istniejącego już kodu. Mimo, że zmiana ta powoduje mnożenie się małych klas, jest to dobre rozwiązanie, ułatwiające oraz skracające prace zwłaszcza w okresie rozszerzania naszej zawartości.

Przykład z rodzajami opłat za booking, każdy rodzaj zawiera różne info ale powinny dziedziczyć z jednej klasy

L – Liskov Substitution Principle

każda klasa bazowa może być zastąpiona dowolną dziedziczącą klasą. Oznacza to że musi być zachowana zgodność interfejsu i wszystkich metod tj. wszystkie jego implementacje muszą poprawnie działać z daną klasą lub metodą.

W poniższym przykładzie utworzona została abstrakcyjna klasa Animal, jednak pojawia się tutaj sytuacja nieprzemyślanego dziedziczenia. O ile Pies jest zwierzęciem i może biegać, o tyle ryba już tego nie potrafi. Przykład ten pokazuje złamanie zasady Liskov, ponieważ klasa Fish nie może wykorzystać fukcji klasy bazowej (możemy ją zdefiniować, ale to nie ma sensu logicznego).

W dobrze stworzonym dziedziczeniu, klasy pochodne nie powinny nadpisywać klas bazowych. Ewentulanie mają możliwość je rozszerzać, podobnie jak to zaprezentowano poniżej:

I – Interface Segregation Principle

klasa nie powinna być zmuszona do implementowania interfejsu, który zawiera metody nie istotne dla niej. Oznacza to, że interfejsy powinny być małe, ale z określonymi metodami, aby klasy nie zawierały metod, których nigdy nie użyją.

A więc wracamy do naszej ulubionej historii z klubem tenisowym. Jak wiadomo bez zapisu informacji związanych z opłatą, nici z rezerwacją i znęcaniem się na korcie tenioswym nad swoim młodszym braciszkiem 😀
Poniżej mamy sytuację zapisu informacji związanych z płatanością. Zapłacić można na wiele sposobów, jak gotówką, kartą, a nawet tzw. internetowym portfelem.

W przypadku określenia jednego interfejsu, każda klasa korzystająca z niego musi zaiplementować wszysktie zawarte w nim funkcje, I tak o to klasa zajmująca się opłatą gotówką musi z definiować funkcję zapisującą dane karty kredytowej, takie jak: numer karty kredytowej, nazwa banku itp. W przypadku jakbyśmy chcieli rozszerzyć naszą oferte o portfel internetowy klasa ta musiałby implementować kolejną funkcje mimo że, podobnie jak wcześniej, nie potrzebujemy tych informacji w transakcji gotówkowej.

W takim razie zgodnie z naszą zasadą, nie zmuszamy klasy do implementowania interfejsu z metodami nie potrzebnymi dla niej, rozbito interfejs na mniejsze fragmenty. Teraz w takim przypadku, klasa cashPayment implementuje tylko interfejs przeznaczony dla opłaty gotówkowej. Klasa CardPayment implementuje metody dwóch interfejsów, tym samym zapisuje informacje zarówno o samej opłacie (imię, nazwisko, wartość itp.) jak również informacje o używanej karcie (numer karty, nazwa banku itp.). Ponadto zmiana ta pozwala nam wprowadzać nowe klasy bez implementowania nie potrzebnych im metod.

D – Dependency Inversion Principle

klasa wysokiego poziomu nie powinna zależeć od klasy niskiego poziomu. Oznacza to, że niskopoziomowe klasy implementują interfejsy określone przez klasę wysokiego poziomu.

W poniszym przykładzie mamy klase Booking z dwoma atrybutami w postaci obiektów. Obiekty te są definiowane dopiero przy wykonywaniu metody zapisującej dane naszej rezerwacji kortu tenisowego.

Rozwiązanie to zmusza naszą klasę Booking (klasa wysokigo poziomu) do decydowania jaki objekt klasy Payment (klasa niskiego poziomu) ma stworzyć. Przypadek ten zaprzecza opisywanej zasadzie, która inaczej mówi, ze odpowiedzialność tworzenia obiektu zależnego powinna być zlecona na zewnątrz do innej klasy.

Poniżej z kolei mamy przykład który spełnia opisywaną zasadę. Juz przy tworzeniu obiektu Booking, definiujemy poprzez argumenty konstruktora z jakim rodzajem płatności mamy do czynienia w czasie rezerwacji kortu tenisowego.

S.O.L.I.D.

Dodaj komentarz

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

Przewiń do góry