SOLID oraz zasady projektowania Flashcards
Jakie są zasady SOLID ?
S - Single-responsiblity Principle
O - Open-closed Principle
L - Liskov Substitution Principle
I - Interface Segregation Principle
D - Dependency Inversion Principle
Na czym polega Single Responsibility Principle ?
Klasa powinna podlegać zmianie tylko z jednego powodu.
Spróbuj uczynić każdą klasę odpowiedzialną za jedną tylko część funkcjonalności oferowanej przez oprogramowanie i niech ta odpowiedzialność będzie całkowicie hermetyzowana.
Głównym celem tej zasady jest redukcja złożoności. Nie trzeba wynajdywać skomplikowanego projektu dla programu któryskłada się tylko z 200 linii kodu. Wystarczy tuzin ładnie napisanych metod.
Przykład Solid Responsibility Principle
- błędem jest że klasa pracownik może być zmodyfikowana z dwóch powodów:
- zarządzanie danymi pracownika
- drukowanie raportu ewidencji
- w przypadku zmiany raportu ewidnecji musisz zmienic kod klasy Employee
Open/Closed Principle
Klasy powinny być otwarte na rozszerzenie ale zamknięte na modyfikacje
- kluczem jest tworzenie abstrakcji, które są trwałe i rozszerzanie tych abstrakcji implementacjami
Open/Closed Principle - kiedy klasa jest otwarta ?
Klasa jest otwarta kiedy bez wpływu na obecny kod:
* można dodawać pola
* można dodawać metody
* tworzyć podklasy
Gdy jakaś klasa jest już opracowana, przetestowana, opisana izawarta w jakimś frameworku lub w inny sposób w aplikacji, to zmiana jej kodu niesie ryzyko. Zamiast zmieniać kod klasy bezpośrednio, tworzy się podklasę i nadpisuje te elementy pierwotnej klasy, których zachowanie chce się zmienić.
Open/Closed Principle - Przykład
- metoda getShippingCost() w klasie Order wymaga modyfikacji przy dodawaniu nowego sposobu przesyłki - klasa jest zamknięta
- tworząc interfejs Shipping oraz konkretne klasy obliczające koszt dla poszczególnych typów nie musimy modyfikować obecnego kodu
Liskov Substitution Principle
Rozszerzając klasę, trzeba pamiętać, aby było możliwe przekazywanie obiektów nowej podklasy w miejsce obiektów klasy bazowej bez psucia kodu klienta.
Nadpisując metodę, należy rozszerzać zachowanie klasy bazowej zamiast zamieniać je na coś zupełnie innego.
- relacje IS-A czasami jest za słaba i należy spojrzeć bardziej na zachowanie obiektów w przypadku dziedziczenia
Przykład - Liskov Substitution Principle
W matematyce kwadrat jest prostokątem, jeśli założymy że Square extends Rectangle może dojść do nieprawidłowego działania
public void resizeRectangle(Rectangle rect, double width, double height) { rect.setWidth(width); rect.setHeight(height); System.out.println("Pole powierzchni: " + rect.area()); }
Rectangle square = new Square(5); resizeRectangle(square, 10, 5); // Oczekujemy, że pole powierzchni będzie wynosić 50, ale faktyczny wynik to 100
Interface Segregation Principle
Klientom nie powinno się narzucać zależności od nieużywanych metod.
Staraj się tworzyć interfejsy na tyle wąsko wyspecjalizowane,żeby klienci nie musieli implementować zachowań których nie potrzebują.
Interface Segregation Principle Przykład
Dependency Inversion Principle
- Wysokopoziomowe klasy nie powinny być zależne od niskopoziomowych
- Obie grupy powinny być zależne od abstrakcji.
Abstrakcje nie powinny być zależne od szczegółów.
Szczegóły z kolei powinny zależeć od abstrakcji. - odseparowanie logiki binesowej od szegółów implementacyjnych
Dependency Inversion Principle - przykład
Zasady dobrego projektowania
- Hermetyzuj to co się różni - identyfikuj aspekty które ulegają zmianą i rodziel je od tego co stałę
- Programuj pod interfejs a nie implementację - opieraj się na abstrakcjach zamiast konkretnych klasach
- Preferuj kompozycje ponad dziedziczenie -
Hermetyzacja na poziomie klasy
- wyekstrachowanie dużej metody do osobnej klasy getTaxRate() -> TaxCalculator
Hermetyzacja na poziomie metody
- rozdzielenie rozbudowanej metody na kilka mniejszych
Programowanie pod interfejs -
Jak uniezależnic jedną klasę od drugiej ?
- Określ czego konkretnie potrzebuje jeden obiekt od drugiego: którą metodę wywołuje?
- Opisz te metody w nowym interfejsie lub klasie abstrakcyjnej.
- Spraw, by klasa stanowiąca zależność implementowała powyższy interfejs.
- Uczyń drugą klasę zależną od tego interfejsu, a nie od konkretnej
klasy. Może ona nadal współdziałać z obiektami pierwotnej klasy, ale połączenie jest teraz elastyczniejsze.
Programowanie pod interfejs - przykład
- Początkowo, klasa Firma jest ściśle sprzęgnięta z konkretnymi
klasami pracowników. Jednakże, mimo różnicy w ich implementacjach, możemy uogólnić pewne metody dotyczące pracy, a następnie wyekstrahować wspólny interfejs dla
wszystkich klas pracowników. - Klasa Firma pozostaje sprzęgnięta z klasami pracowników. To
niedobrze, bo jeśli wprowadzimy nowe typy firm, współpracujące
z innymi typami pracowników, będzie trzeba nadpisać
większość klasy Firma zamiast wykorzystać jej kod ponownie. - Po tej zmianie, klasa Firma stała się niezależna od różnorakich
klas pracowników. Można teraz rozszerzyć tę klasę i wprowadzić
nowe typy firm oraz pracowników, ponownie wykorzystując
część kodu bazowej klasy firmy.
Dlaczego kompozycja jest lepsza od dziedziczenia ?
- ** Nadpisując metody trzeba mieć pewność że nowe zachowanie
jest kompatybilne z tym w klasie bazowej** . Jest to istotne,
gdyż obiekty podklas mogą zostać przekazane do jakiegokolwiek
kodu który oczekuje obiektów klasy bazowej i nie chcemy
tego kodu zepsuć. -
Dziedziczenie psuje hermetyzację klasy bazowej ponieważ wewnętrzne
szczegóły klasy-rodzica stają się dostępne dla podklasy. -
Podklasy są ściśle sprzęgnięte z klasami bazowymi . Każda
zmiana w klasie bazowej może zepsuć funkcjonalność podklas. - **Próba ponownego wykorzystania kodu poprzez dziedziczenie
może doprowadzić do utworzenia równoległych hierarchii
dziedziczenia. ** Dziedziczenie zazwyczaj odbywa się w jednym
wymiarze. Gdy zaczyna odbywać się w kilku płaszczyznach,
trzeba tworzyć mnóstwo kombinacji klas, rozbudowując ich
hierarchię do absurdalnych rozmiarów.
Dlaczego kompozycja jest lepsza od dziedziczenia ? - przykład
- każdy dodatkowy parametr powoduje rozmnożenie
liczby podklas. Jest też mnóstwo powtarzającego się kodu
pomiędzy podklasami, ponieważ podklasa nie może dziedziczyć
po dwóch klasach bazowych jednocześnie - Możesz rozwiązać ten problem stosując kompozycję. Obiektysamochody
zamiast implementować zachowanie samodzielnie,
mogą je delegować innym obiektom
Zalety SRP
- Łatwiejsza w utrzymaniu - jeśli klasa ma więcej niż jedną odpowiedzalność to w przypadku zmian wymaga więcej modyfikacji
- zmniejszanie zależności - więszka odpowiedzialność == więcej zależności == trudność w testowaniu, mniejsza elastyczność
- ułatwia wprowadzanie zmian
zalety OCP
- więszka reużywalność kodu
- odporność na modyfikacji