Wzorce projektowe, czyli zasady dobrego programowania

 Sty, 21 - 2017   brak komentarzy   Wzorce Projektowe

Wzorce Projektowe - Design PatternsWzorzec projektowy to schemat uniwersalnego rozwiązania programistycznego, opisujący często pojawiające się i powtarzalne problemy projektowe. W tym artykule postaram się przedstawić i opisać poszczególne wzorce projektowe, ich podział, zadania oraz dlaczego każdy programista powinien je znać i stosować.

 

 

Wzorce projektowe (design patterns) są abstrakcyjnymi opisami rozwiązań programistycznych. Opisują rozwiązanie problemów, które często występują podczas tworzenia oprogramowania.   Pokazują zależności pomiędzy klasami i obiektami oraz korzyści wynikające z tych zależności. Przedstawiają sposoby projektowania oraz zachowanie poszczególnych klas i obiektów.  Celem stosowania wzorców projektowych jest ułatwienie tworzenia, a następnie modyfikacji i utrzymania kodu źródłowego.

Poniżej postaram się opisać ogólne zasady, jakie powinny być stosowane pisząc dobry kod oraz przedstawie kilkanaście najbardziej znanych, klasycznych wzorców projektowych.

 

SOLID

Robert Cecil Martin zaproponował 5 zasad dobrego programowania, nazywane zasadami lub regułami SOLID, które stały się podstawą pisania dobrego, czystego i czytelnego kodu. Wzorce projektowe dążą do jak najlepszej realizacji tych zasad.

Solid jest to zbiór ogólnych zasad opisujących pięć podstawowych założeń programowania obiektowego. Przestrzeganie tych reguł ma na celu wytwarzanie poprawnego, przejrzystego i łatwo rozszerzalnego kodu, który jest bardziej odporny na błędy i z którym się łatwiej i wydajniej pracuje.

  • [S] Single Responsibility Principle (SRP) – zasada pojedynczej odpowiedzialności – klasa powinna mieć tylko jedną odpowiedzialność (nigdy nie powinien istnieć więcej niż jeden powód do modyfikacji klasy).
  • [O] Open/Closed Principle (OCP) – zasada otwarte-zamknięte – składniki oprogramowania (klasy, moduły, funkcje itp.) powinny być otwarte na rozbudowę, ale zamknięte dla modyfikacji (przy zmianie wymagań nie powinien być zmieniany stary działający kod, ale dodawany nowy, który rozszerza zachowania).
  • [L] Liskov Substitution Principle (LSP) – zasada podstawiania Liskov – korzystanie z funkcji klasy bazowej musi być możliwe również w przypadku podstawienia instancji klas pochodnych.
  • [I] Interface Segregation Principle (ISP) – zasada segregacji interfejsów – wiele dedykowanych interfejsów jest lepsze niż jeden ogólny, reguła ta ma za zadanie przede wszystkich wyeliminowanie nieporęcznych, niepotrzebnie rozbudowanych interfejsów
  • [D] Dependency Inversion Principle (DIP) – zasada odwracania zależności – wysokopoziomowe moduły nie powinny zależeć od modułów niskopoziomowych, zależności między nimi powinny wynikać z abstrakcji.

 

GRASP

Podczas gdy zasady SOLID dotyczą szerszego aspektu programowania, GRASP (General Responsibility Assignment Software Principles) opisuje reguły dotyczące przede wszystkim programowania obiektowego OOP (Object-Oriented Programming). Jest to zbiór 9. zasad mówiących o zasadach przydziału odpowiedzialności do klas.

  • Information Expert (Ekspert) – główna zasada programowania obiektowego, która mówi że odpowiedzialność powinna być przydzielona klasie, która posiada informacje niezbędne do wykonania zadania, tzn. takiej która jest najlepiej do tego zadania przygotowana.
  • Creator (Twórca) – mówi o tym, kto powinien tworzyć obiekt. Klasa B powinna być odpowiedzialna za tworzenie instancji klasy A wtedy gdy: zawiera lub agreguje klasę A, posiada dane inicjujące klasę A, zapamiętuje klasę A lub intensywnie z niej korzysta.
  • Controller (Kontroler) – reguła mówiąca o tym, który obiekt jako pierwszy przyjmuje i koordynuje żądaniami. Odpowiedzialność tą powinien otrzymać obiekt, który reprezentuje system, podsystem lub scenariusz przypadku użycia.
  • Low Coupling (Niskie Sprzężenie) – zasada mówiąca o przydziale odpowiedzialności obiektom, w taki sposób aby zależności (sprzężenia) pomiędzy nimi były jak najmniejsze. Stosowanie tej zasady jest niezwykle ważne ponieważ pozwala zmniejszyć zasięg oddziaływania zmian w aplikacji, a co za tym idzie zmniejszenie ryzyka powstawania błędów. Pozwala również na lepsze zrozumienie kodu.
  • High Cohesion (Wysoka Spójność) – podkreśla to, aby klasy były skoncentrowane na jednym, ściśle określonym zadaniu. Eliminuje to tworzenie nadmiernie rozbudowanych klas, pozwala na lepsze zrozumienie i utrzymanie kodu oraz zmniejsza zależności pomiędzy klasami. Operacje oferowane przez obiekty powinny tworzyć spójną całość.
  • Polymorphism (Polimorfizm) – jedna z najważniejszych cech OOP, mówiąca o różnicowaniu zachowań operacji w klasach. Różnicowanie to powinno następować dzięki polimorficznym wywołaniom operacji w klasach pochodnych.
  • Pure Fabrication (Czysty Wymysł) – opisuje sytuacje, gdy nie jesteśmy w stanie przypisać odpowiedzialności do żadnej z klas opisujących domenę systemu. W takiej sytuacji należy przypisać ten zakres odpowiedzialności sztucznej, pomocniczej klasie, która nie reprezentuje konceptu z dziedziny problemu. Taka klasa nazywana jest serwisem lub usługą.
  • Indirection (Pośrednictwo) – zasada mówiąca o unikaniu bezpośredniego powiązania (zależności). W takiej sytuacji należy przydzielić zobowiązanie obiektowi pośredniczącemu, który będzie przekazywał informacje pomiędzy poszczególnymi komponentami lub usługami. Pozwala to uniknąć nadmiernych, bezpośrednich sprzężeń.
  • Protected Variations (Ochrona Zmienności) – reguła podkreślająca konieczność ochrony przed zmiennymi lub niestabilnymi obiektami, podsystemami lub systemami. Należy zidentyfikować miejsca przewidywanej zmienności lub niestabilności oraz przydzielić zobowiązania w taki sposób, aby wokół tych miejsc powstały stabilne interfejsy.

 

 

Rodzaje wzorców

Podstawowy podział wzorców, klasyfikuje je zgodnie z jego przeznaczeniem, tzn. tym do czego wzorzec jest wykorzystywany. Według tego podziału, możemy wyróżnić trzy typy:

Kreacyjne / Konstrukcyjne – opisujące proces tworzenia nowych obiektów. Ich zadaniem jest tworzenie, inicjalizacja oraz konfiguracja obiektów, klas oraz innych typów danych. Należą do nich wzorce: Budowniczy, Fabryka, Prototyp, Singleton.

Strukturalne – opisujące struktury powiązanych ze sobą obiektów. Do tego typu zaliczamy: Adapter, Dekorator, Fasada, Kompozyt, Most, Pełnomocnik, Pyłek.

Czynnościowe / Behawioralne / Operacyjne – opisujące zachowanie i odpowiedzialność współpracujących ze sobą obiektów. Są to wzorce: Łańcuch odpowiedzialności, Polecenie, Interpreter, Iterator, Mediator, Pamiątka, Metoda szablonowa, Obserwator, Stan, Strategia, Odwiedzający.

 

Wzorce Kreacyjne / Konstrukcyjne

 

Budowniczy – Builder

Kreacyjny wzorzec projektowania, którego zadaniem jest stworzenie obiektu z innych mniejszych obiektów. Celem jest rozdzielenie sposobu tworzenia obiektów od ich reprezentacji. Budowanie obiektu oparte jest na jednym procesie konstrukcyjnym i podzielone jest na mniejsze etapy.
Zaletą wykorzystania tego wzorca jest możliwość łatwego sterowania, w jaki sposób przebiega proces tworzenia obiektów oraz większa skalowalność kodu. Ponadto izolujemy ten proces, który często może być skomplikowany.

Więcej na temat wzorca Budowniczy: http://www.blog.molitorys.pl/wzorce-projektowe-budowniczy

 

Fabryka – Factory

Wzorzec konstrukcyjny, służący do tworzenia nowych obiektów, związanych z jednym, wspólnym interfejsem. Główną zaletą stosowania takiej metody tworzenia nowych obiektów jest uniezależnienie się od konkretnej implementacji oraz od procesu tworzenia instancji. Obiekty tworzone są w przewidywalny sposób, wg. ustalonego interfejsu, przez co proces ten jest scentralizowany oraz hermetyczny.

Jest kilka wersji tego wzorca: prosta fabryka (simple factory), fabryka statyczna (static factory), metoda fabrykująca (factory method), fabryka abstrakcyjna (abstract factory). Różnią się one implementacją, jednak każdy z nich ma za zadanie dostarczyć sposób na tworzenie nowych obiektów.

 

Prototyp – Prototype

Kreacyjny wzorzec projektowy, umożliwiający tworzenie obiektów danej klasy wykorzystując do tego już istniejący wzorcowy obiekt, nazywany prototypem. W tym celu wykorzystywane są mechanizmy klonowania lub kopiowania obiektów.
Wzorzec ten wykorzystywany jest, gdy chcemy utworzyć wiele obiektów tego samego typu lub o podobnych właściwościach oraz gdy chcemy uniezależnić system od sposobu w jaki tworzone są w nim jego produkty.

 

Singleton

Kreacyjny wzorzec projektowania, ograniczający tworzenie obiektów danej klasy do tylko jednej instancji oraz zapewniający globalny dostęp do tego obiektu.

 

Wzorce Strukturalne

 

Adapter

Strukturalny wzorzec projektowy wykorzystywany do połączenia dwóch niekompatybilnych ze sobą interfejsów, tak aby możliwa była współpraca pomiędzy różnymi, nie dostosowanymi do siebie obiektami.
Wzorzec ten szczególnie wykorzystywany jest gdy chcemy wykorzystać zewnętrzną bibliotekę, której interfejs nie jest dostosowany do naszej aplikacji. Przy pomocy adaptera opakowujemy niekompatybilny interfejs takiej biblioteki w nowy i dzięki temu nie musimy modyfikować naszego kodu.

Więcej na temat wzorca Adapter: http://www.blog.molitorys.pl/wzorce-projektowe-adapter

 

Dekorator – Decorator

Strukturalny wzorzec projektowy pozwalający na rozbudowę funkcjonalności istniejącej klasy dynamicznie podczas działania programu. Wzorzec ten pozwala na dodanie lub zmianę zachowania klasy, bez potrzeby jej dziedziczenia.
Wykorzystanie wzorca dekorator polega na opakowaniu klasy bazowej (dekorowanej) w klasę dekorującą.
Zaletą wykorzystania tego wzorca jest bardzo duża elastyczność poprzez rozbicie funkcjonalności na wiele mniejszych klas, które mogą dynamicznie zmieniać działanie klasy bazowej. Wadą natomiast jest rozbicie projektu na małe klasy, które często są do siebie bardzo podobne.

Więcej na temat wzorca Dekorator: http://www.blog.molitorys.pl/wzorce-projektowe-dekorator

 

Fasada – Facade

Strukturalny wzorzec projektowy, którego zadaniem jest dostarczenie ujednoliconego, uproszczonego i uporządkowanego interfejsu programistycznego do złożonego systemu. Innymi słowy wzorzec fasada agreguje elementy złożonego systemu, tworząc większą całość oraz udostępniając łatwy w użyciu interfejs. Implementacja zazwyczaj polega na utworzeniu klasy będącej pośrednikiem pomiędzy klientem a systemem.

Więcej na temat wzorca Fasada: http://www.blog.molitorys.pl/wzorce-projektowe-fasada

 

Kompozyt – Composite

Strukturalny wzorzec projektowy, którego celem jest organizacja obiektów w strukturę (hierarchiczną, drzewiastą grupę obiektów) i zdefiniowanie interfejsu wspólnego zarówno dla pojedynczych obiektów jak i grup obiektów.
W ten sposób klient ma możliwość korzystania ze złożonych struktur obiektów w taki sam sposób jak z obiektów pierwotnych (pojedynczych). Ponadto w łatwy sposób można rozszerzać funkcjonalność programu, dodając nowe struktury (komponenty).

 

Most – Bridge

Strukturalny wzorzec projektowy, którego celem jest oddzielenie abstrakcji (interfejsu) od implementacji obiektu, tak aby mogły zmieniać się niezależnie od siebie. Wzorzec most łączy abstrakcję i implementację, a klient zależy jedynie od abstrakcji (interfejsu) i jest niezależny od implementacji, która może się dynamicznie zmieniać (implementacja jest ukryta przed klientem). Zarówno interfejs jak i implementacja mogą być w dowolny sposób rozszerzane i modyfikowane.

 

Pełnomocnik – Proxy

Strukturalny wzorzec projektowy, którego zadaniem jest zastąpienie (reprezentacje, emulacje) innego obiektu.
Zastosowanie obiektu pełnomocnika jest bardzo różne. Może być stosowany w celu ochrony i kontroli dostępu do obiektów, wykonywania dodatkowych operacji podczas dostępu do nich, kontrolowanego tworzenia na żądanie kosztownych obiektów, cache-owania danych obiektów, itp.

 

Pyłek – Flyweight

Strukturalny wzorzec projektowy, stosowany do tworzenia bardzo dużej liczby identycznych lub podobnych obiektów, którymi można zarządzać w jednolity sposób.
Istotą wzorca jest podział danych przechowywanych w obiektach na wewnętrzne (współdzielone, nie modyfikowane przy inicjacji obiektu) i zewnętrzne (unikatowe dla każdego obiektu, dostarczane z zewnątrz). Współdzielenie obiektów zmniejsza wykorzystanie pamięci i poprawia efektywność programu, ponieważ dane nie są za każdym razem powielane.

 

 

Wzorce Czynnościowe / Behawioralne / Operacyjne

 

Łańcuch Odpowiedzialności / Zobowiązań – Chain of Responsibility

Wzorzec projektowy tworzący listę obiektów, które po kolei analizują żądanie i jeżeli potrafią je obsłużyć, to przystępują do procesu jego realizacji. W przeciwnym wypadku przekazują żądanie dalej, do kolejnego ogniwa w łańcuchu. Łańcuch takich obiektów tworzy listę jednokierunkową, w której odpowiedzialność za przetworzone żądanie spada na najlepiej przygotowany do tego obiekt.

Więcej na temat wzorca Fasada: http://www.blog.molitorys.pl/wzorce-projektowe-lancuch-odpowiedzialnosci

 

Polecenie – Command

Czynnościowy wzorzec projektowy, przy pomocy którego żądanie wykonania czynności traktowane jest jako obiekt, nazywany poleceniem. Obiekty takie rozszerzają wspólny interfejs, przy pomocy którego wykonują lub cofają dane polecenie.
Zaletą stosowania tego wzorca jest zapewnienie większej elastyczności oraz możliwości rozszerzania aplikacji o nowe polecenia. Wadą natomiast jest konieczność tworzenia większej ilości obiektów.

 

Interpreter

Wzorzec projektowy typu czynnościowego, którego zadaniem jest interpretacja poleceń innego języka. Użycie tego wzorca polega na zdefiniowaniu opisu gramatyki sformalizowanego języka interpretowanego i stworzenie dla niego interpretera, dzięki któremu będzie możliwe rozwiązanie danego problemu. Inaczej tłumacząc, wzorzec ten implementuje swojego rodzaju parser, który konwertuje jedną reprezentacje danych w inną.

 

Iterator

Czynnościowy wzorzec projektowy, którego zadaniem jest zapewnienie sekwencyjnego dostępu do elementów zbioru bez ujawnienia jego implementacji wewnętrznej. Po pojęciem zbioru kryją się bardzo różne struktury danych, takie jak listy, kolejki, stosy, zbiory, mapy, tabele, itp. Wzorzec ten ma na celu udostępnienie zunifikowanego interfejsu dostępowego do elementów tych struktur danych.

 

Mediator

Czynnościowy wzorzec projektowy, udostępniający wspólny interfejs dla różnych obiektów w systemie, tak aby te obiekty mogły się ze sobą komunikować. Innymi słowy mediator jest pośrednikiem w komunikacji pomiędzy wieloma obiektami – obiekty te znają tylko mediatora, natomiast mediator zna wszystkie obiekty systemu (topologia gwiazdy).
Główną zaletą stosowania tego wzorca jest zmniejszenie i uproszczenie powiązań pomiędzy różnymi klasami oraz hermetyzacja mechanizmów komunikacji pomiędzy tymi klasami.

 

Pamiątka – Memento

Czynnościowy wzorzec projektowy, pozwalający na zapamiętanie i przechowanie stanu obiektu, a następnie udostępnienie i odtworzenie tego stanu bez naruszania hermetyzacji obiektu.
Zastosowanie tego wzorca ma miejsce w przypadku, gdy chcemy wrócić do konkretnego stanu danego obiektu, w dowolnym momencie oraz gdy chcemy przechować historie obiektów.

 

Metoda Szablonowa – Template Method

Celem tego wzorca projektowego jest zdefiniowanie metody, będącej szkieletem algorytmu. Z reguły ta metoda utworzona jest w klasie abstrakcyjnej i realizuje szereg kroków algorytmu, zdefiniowanych w poszczególnych metodach składowych. Metody składowe algorytmu mogą być następnie redefiniowany w konkretnych klasach pochodnych, ale bez zmiany ogólnej struktury algorytmu.
Zaletą stosowania tego wzorca projektowego jest możliwość zdefiniowania algorytmu, którego wybrane części mogą być w dowolny sposób modyfikowane. Kod nie jest duplikowany, jest przejrzysty, a dodawanie nowych wersji algorytmu jest prostsze i szybsze.

 

Obserwator – Observer

Wzorzec projektowy wykorzystywany, gdy chcemy powiadomić zainteresowane obiekty (obserwatorów) pewną zmianą stanu pojedynczego obiektu (obiekt obserwowany). Struktura taka łączący klasy relacją typu jeden-do-wielu.
Zaletą użycia tego wzorca projektowego jest luźne powiązanie pomiędzy klasami oraz możliwość dynamicznej zmiany tych powiązań. Klasy obserwatorów jak i obiektów obserwujących nie muszą o sobie wiele wiedzieć, przez co nie tworzą się niepotrzebne zależności.

 

Stan – State

Wzorzec czynnościowy, który umożliwia zmianę zachowania obiektu w momencie gdy zmienia się jego wewnętrzny stan. Wszystkie stany są obiektami tymczasowymi i posiadają wspólny interfejs. Aktualny stan obiektu może ulec zmianie pod wpływem wykonania jakiegoś zadania lub upływu określonej ilości czasu.
Zaletą stosowania tego stanu jest łatwa rozbudowa o kolejne stany. Pozwala również uniknąć stosowania bardzo rozbudowanych instrukcji warunkowych.

 

Strategia – Strategy

Wzorzec projektowy, którego zadaniem jest zdefiniowanie w oddzielnych klasach grup algorytmów z danej rodziny. Taka konstrukcja kodu powoduje możliwość wymiennego użycia tych algorytmów (również w trakcie działania aplikacji), ułatwia rozszerzanie funkcjonalności, ogranicza użycie instrukcji warunkowych, ułatwia testowanie poszczególnych rozwiązań algorytmów. Jedną z wad jest zwiększenie ilości implementowanych klas.

Więcej na temat wzorca Strategia: http://www.blog.molitorys.pl/wzorce-projektowe-strategia

 

Odwiedzający / Wizytator – Visitor

Wzorzec projektowy, należący do grupy wzorców czynnościowych, dzięki któremu mamy możliwość oddzielenia kodu implementującego algorytmy od obiektów, w których te algorytmy mają pracować. Dzięki temu można w łatwy sposób dodać nowe rodzaje operacji na obiektach, nie dokonując większych zmian na tych obiektach, a jedynie dopisując nowe algorytmy w zewnętrznych klasach.

 

Podsumowanie

 

Wzorce projektowe są niezależne od języka programowania, a zalet wynikających z ich stosowania jest bardzo dużo. Pozwalają na lepsze zaprojektowanie kodu. Mogą przyśpieszyć proces rozwoju oprogramowania przez dostarczenie wypróbowanych rozwiązań programistycznych, a w przyszłości przez łatwiejsze i wydajniejsze rozszerzanie funkcjonalności.

W kolejnych artykułach będę się starał przedstawić bardziej szczegółowo poszczególne wzorce projektowe wraz z prostymi przykładami ich użycia w języku PHP. Zachęcam do czytania.

 

 

Źródło: http://www.webabc.pl/kategoria/programowanie


Przeczytaj równierz


Dodaj komentarz

Twój adres email nie zostanie opublikowany.