Download as pdf or txt
Download as pdf or txt
You are on page 1of 119

Wzorce projektowe w

przykładach
Czyli jak dobrze programować obiektowo
Agenda
1. Wstęp
2. Czym są wzorce projektowe?
a. Zarys historyczny.
b. Rodzaje wzorców.
c. Czy warto stosować wzorce projektowe?
3. Przypomnienie zasad programowania obiektowego.
a. Cechy złego projektu obiektowego.
b. Podstawowe założenia programowania obiektowego.
c. Zasady SOLID.
4. Przedstawienie wybranych wzorców projektowych.
a. Wzorce konstrukcyjne.
b. Wzorce operacyjne.
c. Wzorce strukturalne.
Wstęp
Wstęp

SOLID OOP
Wstęp - Polecana literatura
Czym są wzorce projektowe?
Zarys historyczny
Czym są wzorce projektowe?

● Zaobserwowane reużywalne zestawy rozwiązań popularnych problemów.


● Wzorzec projektowy składa się z czterech elementów:
○ Nazwa wzorca.
○ Opis problemu, który rozwiązuje.
○ Rozwiązanie.
○ Konsekwencje.
● Wzorce projektowe mają na celu poprawę architektury systemu, zwiększenie
jego elastyczności, reużywalności i czytelności.
● Wzorce projektowe wprowadzają pewnego rodzaju powszechnie uznane
standardy i terminologie.
Klasyczne wzorce projektowe “Gang of four”
● Wzorce konstrukcyjne: ● Wzorce operacyjne:
○ Budowniczy ○ Interpreter
○ Fabryka Abstrakcyjna ○ Iterator
○ Metoda wytwórcza ○ Łańcuch zobowiązań
○ Singleton ○ Mediator
○ Prototyp ○ Metoda szablonowa
● Wzorce strukturalne: ○ Obserwator
○ Adapter ○ Odwiedzający
○ Dekorator ○ Pamiątka
○ Fasada ○ Polecenie
○ Kompozyt ○ Stan
○ Most ○ Strategia
○ Pełnomocnik ○ Pusty Obiekt
○ Pyłek
Czy warto stosować wzorce projektowe?

● Wiedza na temat niektórych wzorców może stać się przedawniona ze


względu na rozwój języków programowania.
● Zanim zdecydujemy się zastosować dany wzorzec musimy być pewni, że
pasuje on do rozwiązywanego problemu.
● Nie możemy dopuścić do sytuacji, w której dopasowywujemy rozwiązanie do
wzorca zamiast wzorzec do rozwiązania.
● Dopuszczalne są odstępstwa od postaci kanonicznej wzorca, jeżeli wymaga
tego nasz problem.
Przypomnienie zasad
programowania obiektowego
Cechy złego projektu obiektowego
● Sztywność - system jest trudny do zmiany, ponieważ każda zmiana wymusza wiele
innych zmian w innych częściach systemu.
● Kruchość - zmiany powodują psucie się systemu w miejscach, które na pozór nie mają
związku z tą częścią, która została zmieniona.
● Brak mobilności - trudno jest rozdzielić system na komponenty, które mogą być użyte
w innych miejscach systemu lub innych systemach.
● Lepkość - stosowanie dobrych praktyk jest trudniejsze niż stosowanie złych praktyk.
● Zbędna złożoność - projekt zawiera infrastrukturę, z której nie ma bezpośredniego
pożytku.
● Zbędne powtórzenia - projekt zawiera powtarzające się struktury, które mogłoby być
ujednolicone w ramach pojedynczej abstrakcji.
● Nieczytelność - system jest trudny do czytania i zrozumienia. Kod nie komunikuje
dobrze swojej intencji.
Podstawowe koncepcje programowania
obiektowego
Podstawowe założenia paradygmatu obiektowego

● Abstrakcja
● Hermetyzacja
● Polimorfizm
● Dziedziczenie
Hermetyzacja

● Hermetyzacja służy jako narzędzie do ochrony niezmienników obiektu. Jest to cecha,


która odróżnia prawdziwe obiekty od prostych struktur danych (anemiczna domena).
● Pozwala na uniezależnienie kodu klienta od zmian następujących w kodzie
zachermetyzowanej klasy.
● Jest narzędziem, które umożliwia nam tworzenie klas niezmiennych.
● Porządku kod systemu. Pozwala na tworzenie pojęciowych modułów i agregatów.
Zasady SOLID
SRP - Single Responsibility
“Nie powinno być nigdy więcej niż jednego powodu do modyfikacji klasy.”

● Każda odpowiedzialność jest osią zmian. Gdy zmienią się wymagania, będzie to
manifestowane poprzez zmianę odpowiedzialności pomiędzy klasami.
● Gdy klasa ma więcej niż jedną odpowiedzialność, to dochodzi do sprzężeń. Zmiany w
jednej odpowiedzialności mogą spowodować regresję w innej (kruchość).
OCP - Open Closed
“Klasa powinna być otwarta na rozszerzenie, ale zamknięta na modyfikacje.”

● Nowe funkcjonalności (np. polityka podatkowa w kraju nowego klienta) nie powinny
powodować zmiany kodu starych struktur poprzez dodanie kolejnego ifa. Nowa
funkcjonalność oznacza kolejną klasę rozszerzającą (polimorfizm).
● Zmiana w jednym miejscu nie powinna powodować kaskadowych zmian w innych
(hermetyzacja).
LSP - Liscov Substitution
“Musi istnieć możliwość podstawienia typów pochodnych za ich typy bazowe.”

● Każda nadklasa powinna przechodzić pozytywnie wszystkie testy, które przechodzą jej
podklasy.
ISP - Interface Segregation
“Wiele dedykowanych interfejsów jest lepsze niż jeden ogólny.”

● Lepiej jest tworzyć wiele małych interfejsów posiadających niewielką ilość metod. Duża
ilość metod powoduje brak spójności.
● Interfejs powinien być dostosowany do potrzeb klienta, który go używa. Interfejs jest
bardziej powiązany z klientem niż z implementacją. Zestaw metod zależy od potrzeb
klienta.
DIP - Dependency inversion
“Wysokopoziomowe moduły nie powinny zależeć od modułów
niskopoziomowych - zależność pomiędzy nimi powinna wynikać z abstrakcji.”

● Zmiany na niższym poziomie nie powinny wywierać wpływu na poziom wyższy.


● Nie powinno dochodzić do wycieku abstrakcji i zależności.
● To komponent na niższym poziomie powinien dostosować się do komponentu na
wyższym.
IoC - Inversion of Control

● Odwrócenie zależności ma trzy podstawowe smaki: Dependency Injection, Eventy,


Aspekty. Każdy kolejny zwiększa poziom odwrócenia kontroli i tym samym
automatyzacje i niezależność komponentów. Zmniejsza jednocześnie nasz poziom
kontroli nad kodem.
Przedstawienie wybranych
wzorców projektowych
Wzorce kreacyjne
Budowniczy

“Oddziela tworzenie złożonego obiektu od jego reprezentacji, dzięki czemu ten


sam proces konstrukcji może prowadzić do powstawania różnych reprezentacji”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Konstruktor teleskopowy
Konstruktor teleskopowy
Konstruktor teleskopowy

● Bardzo łatwo pomylić kolejność argumentów lub konstruktor, który chcemy


wywołać.
● Bardzo trudno jest utrzymać wszystkie niezmienniki obiektu.
● Klasa zaśmiecona setką konstruktorów.
● Właściwie, nie wiemy czym kolejne konstruktory różnią się względem siebie.
Budowniczy (fluent)
Budowniczy (fluent)
Budowniczy (fluent)
● Powinien być wykorzystywany w przypadku obiektów posiadających dużo
opcjonalnych pól (zastąpienie konstruktora teleskopowego).
● Pozwala na tworzenie obiektów, przy użyciu eleganckiej składni wywołań
łańcuchowych, dzięki której widzimy jak na dłoni, które wartości są
inicjalizowane.
● Wymusza niezmienniki.
● Idealnie nadaje się do tworzenia obiektów testowych w testach
automatycznych.
● Tworzenie Budowniczego (Fluent) w Javie można zautomatyzować
wykorzystując adnotacje @Builder z biblioteki lombok.
Budowniczy (postać kanoniczna)

● Powinien być stosowany jeśli algorytm tworzenia złożonego obiektu powinien


być niezależny od składników tego obiektu.
● Powinien być wykorzystywany jeżeli proces konstrukcji musi umożliwiać
tworzenie różnych reprezentacji generowanego obiektu.
● Obiekt stworzony przez budowniczego powinien spełniać wszystkie
niezmienniki.
Budowniczy (postać kanoniczna) - struktura
Budowniczy (postać kanoniczna) - działanie
Budowniczy (postać kanoniczna) - przykład
Budowniczy (postać kanoniczna) - przykład
Budowniczy (postać kanoniczna) - przykład
Singleton

“Gwarantuje, że klasa będzie miała tylko jeden egzemplarz i zapewnia globalny


dostęp do niego.”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Singleton
● Pozwala na tworzenie obiektów, o naturze globalnej, które powinny zawsze
występować w postaci jednego egzemplarza np. kontekst aplikacji
przechowujący instancje dla DI, Servlet despozytora stanowiący punkt
wejściowy zapytań aplikacji webowej.
● Istnienie jednego egzemplarza pozwala na znaczne optymalizacje, ponieważ
nie przechowujemy w pamięci setek instancji np. wartość ZERO w
BigDecimal.
● Używanie tego wzorca może utrudnić testowanie klientów, ponieważ
niemożliwe jest zastąpienie Singletonu imitacją.
Singleton - przykład (eager initialization)

W przypadku ciężkich Singletonów chcemy, aby były tworzone wtedy gdy ich
potrzebujemy.
Singleton - przykład (lazy initialization)

Niestety ta metoda inicjalizacji nie jest bezpieczna dla wielu wątków.


Singleton - przykład (lazy initialization)

Niestety w tym przypadku, każdy obiekt, który ma zainicjalizowany Singleton


musi czekać na zwolnienie blokady.
Singleton - przykład (lazy initialization)

Ta implementacja wykorzystuje wzorzec “double checked locking pattern”.


Można również zastosować blokadę ReetrantLock.
Singleton - przykład (lazy initialization)

Jest to najlepsza implementacja leniwego ładowania Singletonu bezpieczna


dla wątków.
Singleton - przykład (implementacja przez Enum)

Implementacja ta jest bezpieczna dla refleksji, wielowątkowości, serializacji i


wielu kontekstów (testy).

Kotlin posiada wbudowany mechanizm tworzenia Singletonów.


Fabryka/Metoda wytwórcza

“Określa interfejs do tworzenia obiektów przy czym umożliwia podklasom


wyznaczenie klasy danego obiektu. Metoda wytwórcza umożliwia klasom
przekazanie procesu tworzenia egzemplarzy podklasom.”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Fabryka/Metoda wytwórcza - struktura
Fabryka/Metoda wytwórcza - przykład
Fabryka

● Fabryki pozwalają na oddzielenie procesu tworzenia obiektu od jego


struktury.
● Fabryki pozwalają na uzyskanie hermetyzacji. Klient otrzymuje jedynie
informacje o Interfejsie obiektu. Cała implementacja pozostaje przed nim
ukryta i może ona być dowolnie modyfikowana w przyszłości bez obaw o kod
klienta.
● Fabryki są często wykorzystywane w API języka Java np. EnumSet.
● Fabryki są jednym z najbardziej spopularyzowanych wzorców projektowych i
stanowią jeden z Building Blocków taktycznego DDD.
Statyczna metoda fabrykująca

● Posiada nazwę zwiększająca czytelność kodu. Programista wie po nazwie,


która metoda jest odpowiednia.
● Nie powoduje powstania kolejnego przeciążenia konstruktora, które gmatwa
kod klasy.
● Pozwala na wprowadzanie optymalizacji takich jak cache, ograniczona ilość
instancji, zastosowanie wzorca Pyłek itd.
● Umożliwia zwracanie podtypu.
● Bardzo często ta technika jest wykorzystywana w API Javy np. Collections.
● Tak jak klasyczna fabryka umożliwia hermetyzacje przed klientem.
● Stosuje się konwencję nazewniczą valueOf, of, newInstance.
Wzorce operacyjne
Obserwator

“Określa zależność jeden do wielu między obiektami. Kiedy zmieni się stan
jednego z obiektów, wszystkie obiekty zależne od niego są automatycznie
powiadamiane i aktualizowane”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Obserwator - struktura
Obserwator - działanie
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - przykład
Obserwator - wnioski
● Umożliwia luźne powiązanie jednego obiektu z wieloma innymi będącymi
odbiorcami informacji.
● Informacja jest przesyłana automatycznie do wszystkich subskrybentów.
● Ilość odbiorców informacji nie ma znaczenia i mogą oni subskrybować dany
typ informacji oraz go odsubskrybowywać w dowolnym momencie.
● Utrudnia debugowanie i odnajdywanie błędów powodując np. nieoczekiwane
aktualizacje.
● Wzorzec ten jest niezwykle często wykorzystywaną koncepcją w wielu
współcześnie wykorzystywanych bibliotekach. Stanowi podstawę koncepcji
programowania reaktywnego.
Stan

“Umożliwia obiektowi modyfikację zachowania w wyniku zmiany wewnętrznego


stanu. Wygląda tak, jakby obiekt zmienił klasę.”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Stan - przykład
Przykład programowania strukturalnego
Wady strukturalnego podejścia

● Brak hermetyzacji zachowań - “ifologia” związana z danym zachowaniem


rozlana po różnych miejscach systemu
● Brak możliwości zdefiniowania przejść pomiędzy stanami. Stan komponentu
zmieniany jest często przez klienta zamiast przez obiekt, który przechowuje w
sobie stan.
Stan - struktura
Stan - przykład
Stan - przykład
Stan - przykład
Stan - wnioski
● Pozwala na hermetyzacje działań klasy, zależnych od jego statusu. Innymi
słowy “ifologia” spowodowana implementacją stanu poprzez enum bez
zachowań, związana z danym statusem obiektu nie jest rozlana po całym
systemie.
● Umożliwia definiowanie przejść pomiędzy stanami. Obiekt sam definiuje kiedy
zmienia swój stan na inny.
● Należy stosować go z wielką ostrożnością ponieważ może doprowadzić do
powstawania zdenerwowanych metod w sytuacji gdy dane zachowanie nie
jest obsługiwane w danym stanie. Wtedy należy zastanowić się czy obiekt nie
powinien zostać zastąpiony przez inny posiadający inne zachowania zamiast
zmieniać swój wewnętrzny stan.
Strategia/Polityka

“Określa rodzinę algorytmów, hermetyzuje każdy z nich i umożliwia ich zamienne


stosowanie. Wzorzec ten pozwala zmienić algorytmy niezależnie od
korzystających z nich klientów”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Strategia - struktura
Strategia - przykład
Strategia vs Stan
● Wszystkie implementację wzorca Stan są implementacją wzorca Strategia,
ale nie wszystkie implementację wzorca Strategia są implementacją wzorca
Stan.
● Stan zazwyczaj posługuje się referencją do obiektu, który go posiada, aby
móc wpływać na ten obiekt, strategia nie koniecznie.
● W Strategia agregacja (wybór implementacji) jest zazwyczaj ustalana
odgórnie w zależności od potrzeb klienta. W przypadku wzorca Stanu,
implementacja Stanu jest zmieniana w trakcie życia obiektu komponującego
przez niego samego pod wpływem wykonywania na nim operacji.
● Istnienie Stanu nie ma raczej sensu, gdy nie istnieje obiekt, który go posiada
(kompozycja vs agregacja). Stan powinien być ukryty przed klientem i
całkowicie zależy od obiektu, który go posiada.
Strategia - wnioski
● Powoduje powstanie rodziny powiązanych algorytmów, wykonujących pewną
czynność na różne sposoby (klasyczny polimorfizm).
● Delegacja i kompozycja stosowane w przypadku Strategii są bardzo dobrą
alternatywną dla dziedziczenia wykorzystywanego np. we wzorcu Metody
Szablonowej. Zapewnia przy tym znacznie luźniejsze powiązania pomiędzy
klasami niż dziedziczenie (Zachowanie może się zmieniać). Prowadzi do
odwrócenia zależności.
● W połączeniu z Fabryką może stać się skutecznym sposobem na
pozbywanie się z kodu instrukcji warunkowych.
Metoda Szablonowa

“Określa szkielet algorytmu i pozostawia doprecyzowanie niektórych kroków


podklasom. Umożliwia modyfikację niektórych etapów algorytmu w podklasach
bez zmiany jego struktury”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Metoda Szablonowa - struktura
Metoda Szablonowa - przykład
Metoda Szablonowa - wnioski

● Pozwala na eliminacje powtórzeń kodu.


● Może powodować problemy w przypadku, gdy dana podklasa nie wykonuje
pewnego kroku w algorytmie, doprowadzając do tworzenia zdegenerowanych
metod.
● Charakteryzuje się mocniejszym powiązaniem abstrakcji z implementacją niż
Strategia. Sprawia to, że część duplikacji kodu zostaje wyeliminowana
kosztem mniejszej elastyczności (zasada odwrócenia zależności zostaje
częściowo naruszona). Uniemożliwia to dynamiczną zmianę zachowania
klasy.
Pusty obiekt

“In Null Object pattern, a null object replaces check of NULL object instance.
Instead of puttin if check for a null value, Null Object reflects a do nothing
relationship. Such Null object can also be used to provide default behaviour in
case data is not available”

~Tutorials Point
Pusty Obiekt - przykład
Pusty Obiekt
● Zwracanie wartości null z bazy danych lub fabryki powoduje powstanie
bardzo dużej ilości kodu defensywnego, który rozlewa się po całym systemie.
Programiści często zapominają o napisaniu go co powoduje błędy w
systemie.
● To, której instancji Pustego Obiektu użyjemy nie ma żadnego znaczenia,
ponieważ zawsze wykonuje on operacje zdefiniowane przez interfejs w
sposób neutralny, dlatego zaimplementowanie Pustego Obiektu jako
singletonu dodatkowo poprawia wydajność.
Pusty Obiekt - struktura
Pusty Obiekt - przykład
Pusty Obiekt - przykład
Wzorce strukturalne
Kompozyt

“Składa obiekty w struktury drzewiaste odzwierciedlające hierarchię typu


część-całość. Wzorzec ten umożliwia klientom traktowanie poszczególnych
obiektów i ich złożeń w taki sam sposób.”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Kompozyt - struktura
Kompozyt - przykład
Kompozyt - przykład
Kompozyt - struktura
Dekorator

“Dynamicznie dołącza dodatkowe obowiązki do obiektu. Wzorzec ten udostępnia


alternatywny elastyczny sposób tworzenia podklas o wzbogaconych funkcjach.”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Dekorator - struktura
Dekorator - przykład
Dekorator - przykład
Dekorator - przykład
Dekorator - przykład
Dekorator - przykład
Dekorator - wnioski
● Wzbogaca obiekt o nowe funkcje bez zmiany jego wewnętrznej struktury. Jest
nakładką na obiekt, która zawiera właściwy obiekt w środku (Proxy).
● Zapewnia większą elastyczność niż statyczne dziedziczenie. Funkcjonalność
obiektu może być zmieniana dynamicznie w zależności od potrzeb.
● Pozwala na uniknięcie tworzenia przeładowanych funkcjami klas na wysokich
poziomach abstrakcji. Pozwala na zmianę zachowania bez modyfikacji klasy.
● Dekorator ze względu na to, że implementuje ten sam interfejs co klasa, którą
dekoruje jest niewidoczną otoczką dla kodu klienta.
● Powstawanie wielu małych klas i skomplikowanych struktur.
● Służy do tego samego co wzorzec Wizytator. Różni się od Strategi tym, że
obiekt, który jest wzbogacany o nowe funkcjonalności nie ma świadomości
tego, że jest o nie wzbogacany. W przypadku Strategii, obiekt rozszerzany ma
świadomość wszystkich istnienia rozszerzeń (agreguje je).
Dekorator - struktura
Adapter

“Przekształca interfejs klasy na inny, oczekiwany przez klienta. Adapter umożliwia


współdziałanie klasom, które z uwagi na niezgodne interfejsy standardowo nie
mogą współpracować ze sobą”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Adapter - struktura
Adapter - struktura
Adapter
● Do rysowania tekstu potrzebujemy wsparcia
zewnętrznej biblioteki, której nie jesteśmy w
stanie modyfikować.
● Potrzebna klasa (TextLibraryClass) ma
interfejs niezgodny z wykorzystywanym w
programie interfejsem Shape.
● W celu poradzenia sobie z tym problemem
tworzymy klasę TextShape, która stanowi
adapter dla klasy pochodzącej z zewnętrznej
biblioteki.
● Adaptery pozwalają na uzyskanie pewnej
niezależności względem kodu używanego
przez klientów oraz bibliotek zewnętrznych.
Adapter - przykład
Adapter - przykład
Adapter - przykład
Fasada

“Udostępnia jednolity interfejs dla zbioru interfejsów z podsystemu. Fasada


określa interfejs wyższego poziomu ułatwiający korzystanie z podsystemu.”

~Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku


Fasada - struktura

Fasada skrywa złożoność systemu wystawiając jednolity interfejs ułatwiający korzystanie z niego.
Fasada - przykład
Fasada - przykład
Fasada
● Fasada jest niezwykle prostym i intuicyjnym wzorcem projektowym.
● Przykłady jej wykorzystania można mnożyć zarówno na poziomie bibliotek jak
i architektury.
● Fasada oddziela kod klienta od kodu modułu i zapewnia pomiędzy nimi
luźniejsze powiązanie.
● Ułatwia podział systemu na moduły.
● Wszystko co jest izolowane fasadą powinny być niedostępne dla klientów
(dostęp pakietowy, którego w kotlinie nie ma :( ).
Dziękuje za uwagę
Czy są jakieś pytania?

You might also like