PHP. Wzorce projektowe

Ciągły rozwój języka PHP sprawia, że za jego pomocą można już tworzyć bardzo zaawansowane aplikacje. Wszyscy programiści

490 126 6MB

Polish Pages [297] Year 2013

Report DMCA / Copyright

DOWNLOAD FILE

Polecaj historie

PHP. Wzorce projektowe

Table of contents :
Spis treści
Przedmowa
I. Wstęp do wzorców projektowych
1. PHP a programowanie obiektowe
Wstęp do średnio zaawansowanego i zaawansowanego programowania
Dlaczego programowanie zorientowane obiektowo?
W jaki sposób ułatwić rozwiązywanie problemów?
Modularyzacja
Klasy i obiekty
Zasada jednej odpowiedzialności
Konstruktory w PHP
Klient jako klasa żądająca
Ustawienie sposobu wyświetlania błędów w pliku php.ini
Urządzenia mobilne
Co z wydajnością?
Szybkość tworzenia i modyfikacji rozwiązań
Szybkość w zespole
Problemy z programowaniem sekwencyjnym i proceduralnym
Programowanie sekwencyjne
Programowanie proceduralne
Zapłać teraz lub zapłać potem
Abstrakcja
2. Podstawowe zagadnienia programowania obiektowego
Unknown
Klasy abstrakcyjne
Właściwości i metody abstrakcyjne
Interfejsy
Interfejsy i stałe
Klasy abstrakcyjne i interfejsy: dwa typy interfejsów
Podpowiadanie typów: prawie typowanie
Enkapsulacja
Enkapsulacja w życiu codziennym
Zapewnienie enkapsulacji poprzez widoczność
Funkcje ustawiające i pobierające
Dziedziczenie
Polimorfizm
Jedna nazwa, wiele implementacji
Polimorfizm wbudowany we wzorcach projektowych
Tylko spokojnie
Korzystaj z karteczek przyklejanych na stronach
Wprowadzenie do MVC
3. Podstawowe zagadnienia wzorców projektowych
Podstawowe zasady wzorców projektowych
Dlaczego Banda Czterech?
Pierwsza zasada wzorców projektowych
Wykorzystanie typów interfejsowych przy podpowiadaniu typów
Klasy abstrakcyjne i ich interfejsy
Druga zasada wzorców projektowych
Przykład złożoności przy wykorzystaniu klienta
Delegacja: różnica między MA a JEST
Wzorce projektowe jako wielka ściąga
Organizacja wzorców projektowych
Wybór wzorca projektowego
Czym spowodowana jest konieczność powtórnego projektowania?
Co się zmienia?
Unikatowe rozwiązywanie problemów: czy wzorce projektowe to odpowiedzi w puszce?
Czym różnią się wzorce projektowe od frameworków?
4. Wykorzystanie UML-a we wzorcach projektowych
Po co język modelowania UML?
Diagramy klas
Symbole uczestnictwa
Który UML jest najlepszy?
Notacja relacji
Relacje asocjacji
Relacje agregacji
Relacje implementacji i dziedziczenia
Polimorfizm i warunki użycia
Relacje tworzenia
Relacje wielokrotne
Diagramy obiektów
Diagramy interakcji
Rola diagramów i notacji w programowaniu obiektowym
Narzędzia dla UML
Inne diagramy UML
II. Wzorce kreacyjne
5. Wzorzec Metoda Fabrykująca
Czym jest wzorzec Metoda Fabrykująca?
Kiedy korzystać z Metody Fabrykującej
Minimalistyczny przykład
Praca z fabryką
Klient
Wprowadzanie zmian w klasach
Dodanie elementów graficznych
Koordynowanie produktów
Zmiany w produkcie tekstowym
Zmiany w produkcie graficznym
Dodanie nowych produktów i parametryzowanych żądań
Jedna fabryka i wiele produktów
Nowe fabryki
Nowe produkty
Klient z parametrem
Klasy pomocnicze
Diagram plików
Produkty się zmieniają, interfejs zostaw w spokoju
6. Wzorzec Prototyp
Czym jest wzorzec Prototyp?
Kiedy wykorzystać wzorzec Prototyp
Funkcja klonująca
Podczas klonowania konstruktor nie jest wywoływany
W funkcji konstruktora nie powinny być wykonywane żadne zadania
Minimalistyczny przykład
Badanie muszek owocówek
Dodanie do prototypu elementów obiektowych
Nowoczesna organizacja biznesowa
Enkapsulacja w interfejsie
Implementacje interfejsów
Klient
Wprowadzanie zmian, rozbudowa funkcjonalności
Dynamiczne tworzenie instancji obiektów
Wzorzec Prototyp w świecie PHP
III. Wzorce strukturalne
7. Wzorzec Adapter
Czym jest wzorzec Adapter?
Kiedy wykorzystywać wzorzec Adapter
Wzorzec Adapter zaimplementowany przy wykorzystaniu dziedziczenia
Minimalistyczny przykład adaptera klas: wymiana walut
Wzorzec Adapter zaimplementowany przy wykorzystaniu kompozycji
Z komputerów osobistych na urządzenia mobilne
Projekt dla komputerów osobistych i urządzeń mobilnych: najpierw urządzenia mobilne
Adaptery a zmiany
8. Wzorzec Dekorator
Czym jest wzorzec Dekorator?
Kiedy wykorzystywać wzorzec Dekorator
Minimalistyczny przykład dekoratora
Interfejs komponentu
Interfejs dekoratora
Konkretny komponent
Konkretne dekoratory
Klient
Co z opakowywaniem?
Opakowywanie prymitywów
Klasy i funkcje opakowujące wbudowane w PHP
Wzorce projektowe polegające na opakowywaniu
Dekoratory z wieloma komponentami
Wiele konkretnych komponentów
Konkretne komponenty z wieloma stanami i wartościami
Usługi randkowe dla programistów
Interfejs użytkownika (UI) w HTML
Klasa Client przekazująca dane z HTML
Od nazwy zmiennej do instancji obiektu
Dodawanie dekoracji
IV. Wzorce czynnościowe
9. Wzorzec Metoda Szablonowa
Czym jest wzorzec Metoda Szablonowa?
Kiedy wykorzystywać wzorzec Metoda Szablonowa
Wykorzystanie wzorca Metoda Szablonowa z obrazami i podpisami: minimalistyczny przykład
Klasa abstrakcyjna
Klasa konkretna
Klasa Client
Zasada Hollywood
Wykorzystanie Metody Szablonowej z innymi wzorcami projektowymi
Zmniejszenie obowiązków klienta
Uczestnicy wzorca Metoda Szablonowa
Uczestnicy wzorca Metoda Fabrykująca
Hak we wzorcu Metoda Szablonowa
Ustawienie haka
Implementacja haka
Klasa Client i uruchomienie haka
Mały, ale potężny wzorzec Metoda Szablonowa
10. Wzorzec Stan
Czym jest wzorzec Stan?
Kiedy korzystać ze wzorca Stan
Maszyna stanowa
Światło włączone i wyłączone: minimalistyczna implementacja wzorca
Kontekst jest najważniejszy
Stany
Klasa Client wykonuje żądanie przez Context
Dodawanie stanów
Zmiana interfejsu
Zmiana stanów
Aktualizacja klasy Context
Zaktualizowana klasa Client
Nawigator: więcej wyborów i komórek
Ustalenie szablonu stanów dla macierzy
Przygotowanie interfejsu
Kontekst
Stany
Klient wybiera ścieżkę
Wzorzec Stan a PHP
V. MySQL i wzorce projektowe PHP
11. Uniwersalna klasa połączeniowa i wykorzystanie wzorca Proxy dla bezpieczeństwa
Prosty interfejs i klasa do połączeń MySQL
Interfejs w ciąży
Uniwersalna klasa połączeniowa MySQL i zmienne statyczne
Czy zmienne globalne zawsze są złe?
Prosty klient
Proxy zabezpieczające proces logowania
Stworzenie formularza rejestracji
Implementacja proxy logowania
Proxy i bezpieczeństwo w prawdziwym świecie
12. Elastyczny wzorzec projektowy Strategia
Algorytmy enkapsulujące
Różnica między wzorcami Strategia i Stan
Żadnych poleceń warunkowych, proszę
Rodzina algorytmów
Minimalistyczny wzorzec Strategia
Klasa Client i skrypty wyzwalające
Klasa Context i interfejs strategii
Konkretne strategie
Rozszerzony wzorzec Strategia uwzględniający bezpieczeństwo danych i parametryzowane algorytmy
Klasa pomocnicza odpowiedzialna za bezpieczeństwo danych
Dodanie parametru do metody algorithm
Tabela survey
Moduły wprowadzania danych
Klasa Client wzywa pomocy
Drobna, ale ważna zmiana w klasie Context
Konkretne strategie
Elastyczny wzorzec Strategia
Podaj dalej
13. Wzorzec projektowy Łańcuch Odpowiedzialności
Łańcuch Odpowiedzialności w aplikacji pomocy technicznej
Utworzenie i uzupełnienie tabeli odpowiedzi
Łańcuch Odpowiedzialności pomocy technicznej
Zautomatyzowany Łańcuch Odpowiedzialności i Metoda Fabrykująca
Łańcuch Odpowiedzialności i żądania w oparciu o datę
Dlaczego zmieniono funkcje operujące na datach: temporalna wieża Babel
Metoda Fabrykująca wieńczy dzieło
Łatwość aktualizacji
14. Budowa systemu CMS za pomocą wzorca Obserwator
Wbudowane interfejsy Obserwatora
Kiedy korzystać ze wzorca Obserwator
Wykorzystanie SPL we wzorcu Obserwator
SplSubject
SplObserver
SplObjectStorage
Konkretny podmiot SPL
Konkretny obserwator SPL
Klient SPL
Czyste PHP i wzorzec Obserwator
Implementacja klas abstrakcyjnych Subject i ConcreteSubject
Interfejs Observer i wiele konkretnych obserwatorów
Klient
Budowa prostego systemu CMS
Infrastruktura programu
Obserwator dla wielu urządzeń
Myślenie obiektowe
Skorowidz
O autorze

Citation preview

Spis treści

Przedmowa ............................................................................................................................. 13

I Wstęp do wzorców projektowych..........................................................19 1. PHP a programowanie obiektowe............................................................................... 21 Wstęp do średnio zaawansowanego i zaawansowanego programowania Dlaczego programowanie zorientowane obiektowo? W jaki sposób ułatwić rozwiązywanie problemów? Modularyzacja Klasy i obiekty Zasada jednej odpowiedzialności Konstruktory w PHP Klient jako klasa żądająca Co z wydajnością? Szybkość tworzenia i modyfikacji rozwiązań Szybkość w zespole Problemy z programowaniem sekwencyjnym i proceduralnym Programowanie sekwencyjne Programowanie proceduralne Zapłać teraz lub zapłać potem

21 22 22 23 23 24 24 25 28 28 29 29 29 30 30

2. Podstawowe zagadnienia programowania obiektowego.........................................33 Abstrakcja Klasy abstrakcyjne Właściwości i metody abstrakcyjne Interfejsy Interfejsy i stałe Podpowiadanie typów: prawie typowanie

33 34 35 37 38 39

5

Enkapsulacja Enkapsulacja w życiu codziennym Zapewnienie enkapsulacji poprzez widoczność Funkcje ustawiające i pobierające Dziedziczenie Polimorfizm Jedna nazwa, wiele implementacji Polimorfizm wbudowany we wzorcach projektowych Tylko spokojnie

41 41 42 44 46 47 49 50 50

3. Podstawowe zagadnienia wzorców projektowych ................................................... 51 Wprowadzenie do MVC Podstawowe zasady wzorców projektowych Pierwsza zasada wzorców projektowych Wykorzystanie typów interfejsowych przy podpowiadaniu typów Klasy abstrakcyjne i ich interfejsy Druga zasada wzorców projektowych Przykład złożoności przy wykorzystaniu klienta Delegacja: różnica między MA a JEST Wzorce projektowe jako wielka ściąga Organizacja wzorców projektowych Wybór wzorca projektowego Czym spowodowana jest konieczność powtórnego projektowania? Co się zmienia? Czym różnią się wzorce projektowe od frameworków?

51 53 54 54 55 58 58 61 61 62 63 63 63 64

4. Wykorzystanie UML-a we wzorcach projektowych ..................................................65 Po co język modelowania UML? Diagramy klas Symbole uczestnictwa Notacja relacji Relacje asocjacji Relacje agregacji Relacje implementacji i dziedziczenia Relacje tworzenia Relacje wielokrotne Diagramy obiektów Diagramy interakcji Rola diagramów i notacji w programowaniu obiektowym Narzędzia dla UML Inne diagramy UML

6



Spis treści

65 66 67 69 69 71 72 74 74 75 76 77 77 78

II Wzorce kreacyjne ....................................................................................79 5. Wzorzec Metoda Fabrykująca ..................................................................................... 81 Czym jest wzorzec Metoda Fabrykująca? Kiedy korzystać z Metody Fabrykującej Minimalistyczny przykład Praca z fabryką Klient Wprowadzanie zmian w klasach Dodanie elementów graficznych Koordynowanie produktów Zmiany w produkcie tekstowym Zmiany w produkcie graficznym Dodanie nowych produktów i parametryzowanych żądań Jedna fabryka i wiele produktów Nowe fabryki Nowe produkty Klient z parametrem Klasy pomocnicze Diagram plików Produkty się zmieniają, interfejs zostaw w spokoju

81 82 83 83 85 86 86 87 88 89 90 91 91 92 93 94 95 96

6. Wzorzec Prototyp.........................................................................................................99 Czym jest wzorzec Prototyp? Kiedy wykorzystać wzorzec Prototyp Funkcja klonująca Podczas klonowania konstruktor nie jest wywoływany W funkcji konstruktora nie powinny być wykonywane żadne zadania Minimalistyczny przykład Badanie muszek owocówek Dodanie do prototypu elementów obiektowych Nowoczesna organizacja biznesowa Enkapsulacja w interfejsie Implementacje interfejsów Klient Wprowadzanie zmian, rozbudowa funkcjonalności Dynamiczne tworzenie instancji obiektów Wzorzec Prototyp w świecie PHP

Spis treści

99 99 100 102 103 103 103 106 107 107 108 111 113 114 115



7

III Wzorce strukturalne ..............................................................................117 7. Wzorzec Adapter.........................................................................................................119 Czym jest wzorzec Adapter? Kiedy wykorzystywać wzorzec Adapter Wzorzec Adapter zaimplementowany przy wykorzystaniu dziedziczenia Minimalistyczny przykład adaptera klas: wymiana walut Wzorzec Adapter zaimplementowany przy wykorzystaniu kompozycji Z komputerów osobistych na urządzenia mobilne Adaptery a zmiany

119 120 122 122 126 126 133

8. Wzorzec Dekorator .................................................................................................... 135 Czym jest wzorzec Dekorator? Kiedy wykorzystywać wzorzec Dekorator Minimalistyczny przykład dekoratora Interfejs komponentu Interfejs dekoratora Konkretny komponent Konkretne dekoratory Klient Co z opakowywaniem? Opakowywanie prymitywów Klasy i funkcje opakowujące wbudowane w PHP Wzorce projektowe polegające na opakowywaniu Dekoratory z wieloma komponentami Wiele konkretnych komponentów Konkretne komponenty z wieloma stanami i wartościami Usługi randkowe dla programistów Interfejs użytkownika (UI) w HTML Klasa Client przekazująca dane z HTML Od nazwy zmiennej do instancji obiektu Dodawanie dekoracji

135 136 137 137 137 138 139 141 142 142 142 143 143 144 144 145 150 154 155 155

IV Wzorce czynnościowe........................................................................... 157 9. Wzorzec Metoda Szablonowa................................................................................... 159 Czym jest wzorzec Metoda Szablonowa? Kiedy wykorzystywać wzorzec Metoda Szablonowa Wykorzystanie wzorca Metoda Szablonowa z obrazami i podpisami: minimalistyczny przykład Klasa abstrakcyjna Klasa konkretna 8



Spis treści

159 160 160 161 161

Klasa Client Zasada Hollywood Wykorzystanie Metody Szablonowej z innymi wzorcami projektowymi Zmniejszenie obowiązków klienta Uczestnicy wzorca Metoda Szablonowa Uczestnicy wzorca Metoda Fabrykująca Hak we wzorcu Metoda Szablonowa Ustawienie haka Implementacja haka Klasa Client i uruchomienie haka Mały, ale potężny wzorzec Metoda Szablonowa

162 162 164 165 166 167 169 171 171 172 174

10. Wzorzec Stan...............................................................................................................175 Czym jest wzorzec Stan? Kiedy korzystać ze wzorca Stan Maszyna stanowa Światło włączone i wyłączone: minimalistyczna implementacja wzorca Kontekst jest najważniejszy Stany Klasa Client wykonuje żądanie przez Context Dodawanie stanów Zmiana interfejsu Zmiana stanów Aktualizacja klasy Context Zaktualizowana klasa Client Nawigator: więcej wyborów i komórek Ustalenie szablonu stanów dla macierzy Przygotowanie interfejsu Kontekst Stany Klient wybiera ścieżkę Wzorzec Stan a PHP

175 176 177 178 178 181 182 183 183 184 186 187 188 189 189 190 192 197 198

V MySQL i wzorce projektowe PHP .........................................................199 11. Uniwersalna klasa połączeniowa i wykorzystanie wzorca Proxy dla bezpieczeństwa.................................................................................................... 201 Prosty interfejs i klasa do połączeń MySQL Interfejs w ciąży Uniwersalna klasa połączeniowa MySQL i zmienne statyczne Prosty klient

201 202 202 204

Spis treści



9

Proxy zabezpieczające proces logowania Stworzenie formularza rejestracji Implementacja proxy logowania Proxy i bezpieczeństwo w prawdziwym świecie

204 205 209 214

12. Elastyczny wzorzec projektowy Strategia .................................................................217 Algorytmy enkapsulujące Różnica między wzorcami Strategia i Stan Żadnych poleceń warunkowych, proszę Rodzina algorytmów Minimalistyczny wzorzec Strategia Klasa Client i skrypty wyzwalające Klasa Context i interfejs strategii Konkretne strategie Rozszerzony wzorzec Strategia uwzględniający bezpieczeństwo danych i parametryzowane algorytmy Klasa pomocnicza odpowiedzialna za bezpieczeństwo danych Dodanie parametru do metody algorithm Tabela survey Moduły wprowadzania danych Klasa Client wzywa pomocy Drobna, ale ważna zmiana w klasie Context Konkretne strategie Elastyczny wzorzec Strategia

217 218 219 219 219 221 223 224 227 227 230 230 231 235 237 237 242

13. Wzorzec projektowy Łańcuch Odpowiedzialności...................................................245 Podaj dalej Łańcuch Odpowiedzialności w aplikacji pomocy technicznej Utworzenie i uzupełnienie tabeli odpowiedzi Łańcuch Odpowiedzialności pomocy technicznej Zautomatyzowany Łańcuch Odpowiedzialności i Metoda Fabrykująca Łańcuch Odpowiedzialności i żądania w oparciu o datę Metoda Fabrykująca wieńczy dzieło Łatwość aktualizacji

245 247 247 251 256 256 261 265

14. Budowa systemu CMS za pomocą wzorca Obserwator ........................................... 267 Wbudowane interfejsy Obserwatora Kiedy korzystać ze wzorca Obserwator Wykorzystanie SPL we wzorcu Obserwator SplSubject SplObserver SplObjectStorage 10



Spis treści

267 268 269 270 270 271

Konkretny podmiot SPL Konkretny obserwator SPL Klient SPL Czyste PHP i wzorzec Obserwator Implementacja klas abstrakcyjnych Subject i ConcreteSubject Interfejs Observer i wiele konkretnych obserwatorów Klient Budowa prostego systemu CMS Infrastruktura programu Obserwator dla wielu urządzeń Myślenie obiektowe

271 272 273 274 274 276 277 278 279 283 294

Skorowidz .............................................................................................................................295

Spis treści



11

12



Spis treści

Przedmowa

PHP jest coraz częściej wybieranym przez programistów językiem programowania działającym po stronie serwera, konieczne jest więc wprowadzenie do użycia profesjonalnych technik i struktur programistycznych. Wzorce projektowe, koncept zapożyczony z książki The Timeless Way of Building napisanej przez Christophera Alexandra, odnoszą się do ogólnego rozwiązania problemu powszechnie występującego w danym kontekście. W codziennej pracy programiści PHP napotykają „powszechnie występujące problemy”, a wykorzystanie wzorców projektowych PHP może być na nie lekarstwem. Wzorce projektowe to po prostu narzędzia wykorzystywane do radzenia sobie z realiami profesjonalnego tworzenia oprogramowania. Nie są bibliotekami ani szablonami, ale raczej ogólnymi strukturami, które można wykorzystać do rozwiązywania problemów. Osobiście myślę o wzorcach projektowych w ten sam sposób, w jaki myślę o pętlach. Pętla jest wykorzystywana, kiedy konieczna jest iteracja. Można oczywiście poradzić sobie z iteracją w inny sposób, ale pętla jest wygodnym narzędziem pozwalającym oszczędzać czas (jest też bardziej elegancka niż powtarzanie tej samej linii kodu 1000 razy!). Co więcej, nie czuję się ograniczony wzorcami projektowymi — nie są one gotowymi rozwiązaniami w większym stopniu niż pętla. Mogę wykorzystywać pętlę na wiele sposobów, od pętli for po pętlę while. Analogicznie wzorce projektowe mogą być implementowane na wiele sposobów, zależnie od problemu, jaki mają rozwiązywać. Najważniejszym powodem, dla którego warto stosować wzorce projektowe, jest jednak to, że zapewniają rozwiązanie dla skomplikowanych problemów. Kiedy program staje się większy, niemal zawsze staje się też bardziej skomplikowany. W programowaniu obiektowym ta złożoność jest w pewien sposób ograniczana, ponieważ mamy do czynienia z enkapsulowanymi modułami, ale w programowaniu sekwencyjnym lub proceduralnym nawet drobne zmiany mogą spowodować, że program zawali się jak domek z kart. Wzorce projektowe nie tylko zapewniają rozwiązanie często występujących problemów, ale także dzięki możliwości luźnego łączenia obiektów pozwalają na swobodne wprowadzania zmian nawet w dużych i skomplikowanych programach. Kiedy więc wprowadzana jest zmiana, zamiast rozpoczynać programowanie od początku, możesz po prostu dodać niezbędne elementy, a całość będzie nadal działać — nawet w dużych i skomplikowanych projektach. Wzorce projektowe są także tworzone z myślą o ich wielokrotnym wykorzystaniu. Programiści wykorzystują przecież te same algorytmy w bardzo wielu programach. Dlaczego nie wykorzystywać w ten sposób także większych struktur? Frameworki i szablony są wprawdzie

13

zaprojektowane właśnie po to, aby wielokrotnie je wykorzystywać, są jednak często zbyt szczegółowe. Właśnie w tym miejscu wkracza wielokrotne wykorzystanie wzorców projektowych w PHP, szczególnie w przypadku dużych i skomplikowanych programów. Ponieważ wprowadzanie zmian jest w przypadku wzorców projektowych łatwe, ich powtórne wykorzystanie przy rozwiązywaniu problemów tego samego rodzaju także nie jest skomplikowane. Ograniczanie czasu tworzenia oprogramowania i wykorzystania zasobów pozwala oszczędzać pieniądze i służy Twoim klientom. Programy, które klienci otrzymują, spełniają swoje funkcje, mają poprawną strukturę, pozwalają na bezproblemowe wprowadzanie zmian (klienci zawsze żądają zmian!) i są zbudowane na solidnych podstawach, co zmniejsza prawdopodobieństwo ich awarii.

Do kogo skierowana jest ta książka? W pewnym momencie każdy dobry programista zdaje sobie sprawę, że musi zrezygnować z nawyków programowania sekwencyjnego i proceduralnego. Kolejnym krokiem jest programowanie obiektowe, a zmiana taka wymaga zmiany sposobu myślenia: nie można już patrzeć na program jak na serię poleceń, trzeba skupić się na interakcji i komunikacji pomiędzy obiektami. Wzorce projektowe łączą zasady programowania obiektowego w struktury, które mogą być wielokrotnie wykorzystywane. Są narzędziami profesjonalnych programistów. Ponieważ wzorce projektowe dla programowania powstały przy współpracy teoretyków i praktyków, nie tylko rozwiązują pojedyncze problemy, ale w tym samym czasie można je z powodzeniem stosować w komercyjnej praktyce. Wzorcami projektowymi PHP powinni zainteresować się profesjonalni programiści PHP, który chcą zoptymalizować czas, w jakim powstaje ich kod, uczynić kod zdatnym do wielokrotnego wykorzystania oraz zapewnić swoim klientom rozwiązanie o wysokiej jakości. Książka ta jest w dużej mierze przeznaczona dla osób, które czuły radość, kiedy programowanie było dla nich nowe. Jest dla programisty, który z przyjemnością pracował całą noc nad programem tylko dlatego, że było to dla niego interesujące, kładł się do łóżka i rozpoczynał tworzenie nowego programu, gdy tylko się obudził. Kiedy programowanie jest czymś nowym, a każdy dzień obiecuje nowe odkrycia, które dla programisty są coraz bardziej wymagające i zmuszają go do walki na śmierć i życie, do wykorzystania umysłu w złożony i ekscytujący sposób — jest to przeżycie niczym ze szkoły zen. Jeśli to kiedyś czułeś, będziesz wiedział, o czym piszę. Nie można tego opowiedzieć ani wyjaśnić (nie potrafię tego wyjaśnić nawet sobie i nie wiem, dlaczego ekscytują mnie wyzwania i nagrody, jakie daje programowanie). Wzorce projektowe przywracają wyzwania umysłowe, a ta książka nie jest dla osób, które dopiero zapoznają się z PHP lub programowaniem. Jeżeli dopiero zaczynasz naukę programowania PHP, zanim zaczniesz zapoznawać się ze wzorcami projektowymi, zapoznaj się z książką Robina Nixona Learning PHP, MySQL, JavaScript, and CSS, 2nd Edition. Niniejsza książka (oraz każda inna przyzwoita książka o wzorcach) nie obiecuje, że po mistrzowsku opanujesz wzorce szybko i łatwo. Taka nauka to podróż i trzeba nauczyć się z niej cieszyć. Potrzebny jest czas i wysiłek intelektualny.

14



Przedmowa

Założenia w tej książce Zakładam, że potrafisz programować w PHP i chcesz podnieść swoje umiejętności programistyczne o następne kilka poziomów. W zasadzie zakładam, że jesteś całkiem niezłym programistą, korzystałeś z MySQL i potrafisz korzystać z języka HTML i CSS. Zakładam także, że rozumiesz, iż nie nauczysz się wzorców projektowych PHP w jedno popołudnie. Nauka wzorców projektowych jest podobna do powolnej metamorfozy.

Zawartość książki Książka została podzielona na pięć części. Część I jest przypomnieniem albo wprowadzeniem do programowania obiektowego: Rozdział 1. stanowi wprowadzenie do programowania obiektowego oraz do tego, jak ułatwić sobie skomplikowane zadania programistyczne dzięki modularyzacji. Rozdział 2. omawia podstawowe zagadnienia programowania obiektowego, takie jak abstrakcja, enkapsulacja, dziedziczenie i polimorfizm, oraz struktury PHP implementujące te koncepcje. Rozdział 3. opisuje podstawy wzorców projektowych, ich kategoryzację oraz sposób wyboru konkretnych wzorców do konkretnych zadań. Rozdział 4. wprowadza język modelowania UML (Unified Modeling Language) oraz wyjaśnia, w jaki sposób jest on wykorzystywany w książce. Część II przedstawia wzorce kreacyjne: Rozdział 5. zajmuje się wzorcem Metoda Fabrykująca, którego zasięgiem jest klasa, a celem kreacja. Przykłady dotyczą dynamicznego tworzenia stron wyświetlających grafikę oraz tekst. Rozdział 6. pokazuje, jak korzystać ze wzorca Prototyp, którego zasięgiem jest obiekt, a celem kreacja. Wzorzec Prototyp jest wykorzystywany, kiedy jeden obiekt tworzony jest jako prototyp, na którego podstawie klonowane są kolejne obiekty. Część III omawia wzorce strukturalne: Rozdział 7. ilustruje sposób wykorzystania wzorca Adapter o zasięgu zarówno klas, jak i obiektów. Przykłady pokazują, w jaki sposób w istniejącej strukturze wprowadzić zmiany, aby programista mógł dodawać nowe funkcjonalności. Rozdział 8. wyjaśnia, w jaki sposób istniejący obiekt może być zmieniany bez zaburzania aktualnego działania programu, przy wykorzystaniu wzorca Dekorator. Zobaczysz, jak dekorować obiekty dla mężczyzny i kobiety różnymi właściwościami wymaganymi na stronie randkowej. Część IV skupia się na wzorcach czynnościowych: Rozdział 9. pokazuje, jak korzystać z wzorca Metoda Szablonowa — jednego z najłatwiejszych (do utworzenia i zastosowania) wzorców projektowych. Zobaczysz także, jak we wzorcach projektowych działa słynna Zasada Hollywood. Dodatkowo w tym rozdziale do rozwiązania jednego problemu zostaną wykorzystane dwa wzorce. Rozdział 10. przedstawia wzorzec Stan oraz korzystanie z diagramów stanów do mapowania procesów i zmian stanów. Zawartość książki



15

Część V wprowadza cztery dodatkowe wzorce czynnościowe wykorzystywane w połączeniu z bazą MySQL: Rozdział 11. zawiera uniwersalną klasę połączeniową oraz wzorzec Proxy pozwalający na zwiększenie bezpieczeństwa nazw i haseł użytkowników przechowywanych w bazie danych. Rozdział 12. wyjaśnia różnice między wzorcem Strategia a wzorcem Stan, które mają dokładnie takie same diagramy klas. Przykład z ankietą pokazuje, w jaki sposób można wykorzystać wzorzec Strategia z różnymi żądaniami MySQL. Rozdział 13. przedstawia kilka przykładów wzorca Łańcuch Odpowiedzialności, od aplikacji pomocy technicznej do wykorzystania daty w celu automatycznego wyświetlania (w połączeniu ze wzorcem Metoda Fabrykująca) grafiki i tekstu. Rozdział 14. pokazuje, w jaki sposób wykorzystać wbudowane na potrzeby wzorców projektowych interfejsy PHP. Implementując wzorzec Obserwator, można skorzystać z interfejsów biblioteki SPL (Standard PHP Library). Kolejny przykład przedstawia sposób wykorzystania wzorca Obserwator i samodzielnie stworzonych interfejsów w celu zbudowania prostego systemu CMS.

Konwencje wykorzystywane w książce W książce zostały wykorzystane następujące konwencje typograficzne: Czcionka pogrubiona Wskazuje na nowe, ważne pojęcia. Kursywa Wskazuje na adresy URL, adresy e-mail, nazwy i rozszerzenia plików. Czcionka o stałej szerokości znaków

Wykorzystywana w listingach programów oraz w odniesieniu do elementów programu takich jak zmienne, nazwy funkcji, baz danych, typów danych, zmiennych środowiskowych, poleceń i słów kluczowych. Pochylona czcionka o stałej szerokości znaków

Oznacza tekst, który powinien zostać zastąpiony wartościami dostarczonymi przez użytkownika lub wartościami wynikającymi z kontekstu. Taka ikona oznacza wskazówkę lub uwagę ogólną.

Taka ikona symbolizuje ostrzeżenie, wskazówkę lub uwagę ogólną.

16



Przedmowa

Przykłady kodu Przedstawione w książce przykłady kodu znajdziesz na stronie internetowej tej książki, pod adresem http://www.helion.pl. Możesz wykorzystać je w swoich programach i dokumentacji. Nie musisz kontaktować się z nami z pytaniem o zgodę na wykorzystanie, chyba że planujesz wykorzystać znaczącą część kodu. Na przykład pisanie programu, który wykorzystuje kilka fragmentów kodu z tej książki, nie wymaga zgody. Sprzedaż lub dystrybucja płyty CDROM z przykładami z tej książki wymaga zgody. Udzielenie odpowiedzi na pytanie z wykorzystaniem cytatu z książki lub przykładu kodu nie wymaga zgody. Wykorzystanie znaczącej ilości kodu przykładów z książki w dokumentacji swojego produktu wymaga zgody. Docenimy, ale nie wymagamy umieszczenia informacji o tym, skąd pochodzą wykorzystane informacje. Taka informacja z reguły zawiera tytuł, autora, wydawcę i numer ISBN. Na przykład: PHP. Wzorce projektowe, William Sanders (Helion), 978-83-246-7455-8. Jeżeli uważasz, że wykorzystanie kodu wykracza poza zakres nie wymagający zgody, skontaktuj się z nami pod adresem e-mail: [email protected].

Podziękowania Chciałbym podziękować wszystkim, którzy mi pomogli. Moi koledzy z Uniwersytetu Hartforda byli zawsze pomocni, kiedy miałem wątpliwości dowolnego rodzaju. Profesor John Gray, szef departamentu, wspierał mnie i pomagał jak zawsze. Dr Brian Dorn, mój sąsiad z biura obok, do którego trafiała znaczna część moich pytań, był usłużny, dobrze zorientowany i cierpliwy. Miałem szczęście poznać Michaela Bourque z bostońskiej grupy PHP na konferencji Northeast PHP Conference i doceniam jego wsparcie przy tym projekcie. Nie mogę się już doczekać kolejnej okazji do pracy z nim i bostońską grupą PHP. O’Reilly Media zapewniło trzech zdolnych korektorów merytorycznych. Robin Nixon, autor książki Learning PHP, MySQL & JavaScript, 2nd Edition, zaproponował poprawki, sugestie i wskazówki dotyczące PHP, które pomogły poprawić wiele aspektów kodu. Aaron Saray, autor książki Professional PHP Design Patterns, był bardzo szczegółowy i hojny, jeżeli chodzi o wskazówki. Ma doskonałe oko do szczegółów i dostrzega nawet najmniejsze wady. Aaron i ja mamy bardzo odmienne podejście do wzorców projektowych, ale takie różnice pomagają spojrzeć szerzej programistom zainteresowanym wzorcami projektowymi. Jako korektor sprawdził się również Dmitry Sheiko, prowadzący swój własny blog (http://bit.ly/UGYah7), na którym znajdziesz informacje dotyczące jego podejścia do wzorców projektowych. Redaktor prowadząca, Rachel Roumeliotis, połączyła ze sobą wszystkie elementy i pchnęła projekt do przodu. Maria Gulick, kolejna utalentowana redaktorka z O’Reilly, dbała o szczegóły, kiedy projekt przechodził kolejne korekty. Adiustatorka Jasmine Kwityn znajdowała i poprawiała szczegóły, o których istnieniu — w naszej czy dowolnej innej galaktyce — nawet nie wiedziałem. Cały proces został zainicjowany przez Margot Maley Hutchison z Waterside Productions i jestem jej za to bardzo wdzięczny. Moja żona, Delia, była bardziej wyrozumiała niż większość żon, szczególnie że niedawno wydała własną książkę i wiedziała, jak to działa. Nasz szwajcarski pies pasterski, WillDe, nic sobie za to nie robił z tworzenia tej książki. Dopóki dostawał przysmaki, godził się na wszystko.

Podziękowania



17

18



Przedmowa

CZĘŚĆ I

Wstęp do wzorców projektowych

Każdy kompromis to sztuka dawania i brania, nie może być jednak mowy o braniu i dawaniu w przypadku zagadnień fundamentalnych. Każdy kompromis w takim przypadku to poddanie się. Oznacza samo dawanie i żadnego brania. — Mahatma Gandhi Ludzie mówią o rzeczach fundamentalnych i najważniejszych, a potem zmieniają drobne szczegóły. — Oliver Wendell Holmes jr Przekleństwem tego świata jest to, że dobre nawyki dużo łatwiej porzucić niż złe. — W. Somerset Maugham

Nawyki programisty Odkąd zacząłem regularnie programować, wykształciłem pewne nawyki, które przez lata zmieniały się z programowania sekwencyjnego na programowanie proceduralne i krzyżowały. Częściowo powodem tego były zmiany w językach, z których korzystałem. Zacząłem jeszcze na studiach od Fortrana II, następnie programowałem w Basicu, FORTH, PostScript, a potem w asemblerze i językach maszynowych. Bardziej interesowała mnie nauka nowych języków niż zostanie dobrym programistą. Potem wraz z nadejściem internetu pojawiły się między innymi Java, JavaScript, PHP, C# i ActionScript 3.0. Większość tych języków była (częściowo) oparta na strukturach C++. Były to nowe języki, ale pozostałem przy starych nawykach. Przez przypadek zapoznałem się z koncepcją maszyn stanowych doktora Jonathana Kaye. Zamiast myśleć w kategoriach kontroli przepływu, zacząłem myśleć w kategoriach różnych stanów. Następnie odkryłem wzorzec Stan i książkę Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku Ericha Gammy, Richarda Helma, Ralpha Johnsona i Johna Vlissidesa (Helion, 2010). Przykłady z książki pisane były w języku SmallTalk lub C++. Nie znałem języka SmallTalk, a C++ znałem bardzo słabo, musiałem więc skupić się na części koncepcyjnej. Co ciekawe, była to najlepsza rzecz, jaka mogła się zdarzyć, ponieważ nie musiałem rozpatrywać przykładów w żadnym konkretnym języku. Kiedy zająłem się PHP, nie musiałem tłumaczyć przykładów z języka SmallTalk, musiałem zastosować paradygmat programowania obiektowego i wzorce projektowe bezpośrednio w PHP.

19

Pomału, ale konsekwentnie moje nawyki programistyczne zaczęły się zmieniać. Dodając w moich programach fragmenty obiektowe i wzorce projektowe, nie chciałem już programować w żaden inny sposób. Zdaniem psychoanalityków nawyk formuje się średnio 66 dni, jednak w moim przypadku zmiana zachodziła dłużej i była bardziej stopniowa. Był to dla mnie okres bardzo pracowity i jeżeli miałem do wyboru zakończenie projektu w terminie i skorzystanie z wzorców projektowych, zawsze zwyciężała presja czasu. Coraz częściej jednak sięgałem po rozwiązania obiektowe i z czasem moi klienci otrzymywali programy tworzone w duchu programowania obiektowego i wzorców projektowych. Cztery rozdziały pierwszej części tej książki mają za zadanie pomóc Ci rozpocząć drogę programowania obiektowego:  PHP a programowanie obiektowe  Podstawowe zagadnienia programowania obiektowego  Podstawowe zagadnienia wzorców projektowych  Wykorzystanie UML-a we wzorcach projektowych

Skup się na treści, nie stylu Większość znanych mi dobrych programistów ma specyficzny styl, wskazujący na nawyki profesjonalnego programowania. Przeważnie, kiedy spotkasz się z poprawnym programowaniem obiektowym, rozpoznasz pewien konkretny sposób działania, od nazywania zmiennych po komentowanie kodu. Nazwy zmiennych są jasne, a komentarze jednoznacznie pokazują innym programistom, jak wykorzystać kod we własnych modułach. W tej książce liczba komentarzy w kodzie została ograniczona do minimum — ich funkcję spełnia treść książki. Zauważyłem także, że zbyt duża liczba komentarzy może przeszkadzać w szybkim zrozumieniu struktury kodu. Aby więc można było patrzyć na klasy jako całość, kod nie został poprzedzielany komentarzami (w przypadku programów pisanych nie na potrzeby książki jestem zwolennikiem dużej ilości komentarzy). Z jakiegoś powodu PHP zostało skażone dużą ilością złych przykładów wzorców projektowych. Nie chodzi o głupie przykłady. Mówię o wzorcach projektowych, które pozbawione są pewnych części. Na przykład wzorzec Strategia napisany bez udziału Kontekstu jest złym przykładem. Jest zwyczajnie nietrafiony, podobnie jak każdy niekompletny wzorzec. To tak jak pisanie pętli bez warunku jej zakończenia. Wzorzec Strategia wymaga Kontekstu, tak jak pętla wymaga warunku jej zakończenia. Aby maksymalnie skupić się na celu, wykorzystałem źródła wzorców projektowych omawianych w książce Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku. Język UML (Uniform Modeling Language) jest taki sam jak ten wykorzystany we Wzorcach projektowych. Od tamtego czasu pojawiły się jego nowsze wersje (UML2), jednak w nauce wzorców projektowych w PHP oraz zrozumieniu tych nieopisanych w książce pomoże nauka oryginalnego języka.

20



Rozdział 1. PHP a programowanie obiektowe

ROZDZIAŁ 1.

PHP a programowanie obiektowe

Nie ma nic potężniejszego od idei, na którą nadszedł czas. — Victor Hugo Nie módl się o zadania odpowiadające twoim możliwościom. Módl się o możliwości odpowiadające twoim zadaniom. — Phillips Brooks Dogłębna moc jest uzyskiwana poprzez ciągłe zapewnianie samego siebie w myślach, że urodziło się po to, aby kontrolować. — Andrew Carnegie Ignorancja jest przekleństwem Boga; wiedza to skrzydła, na których wznosimy się ku niebu. — William Shakespeare

Wstęp do średnio zaawansowanego i zaawansowanego programowania Kiedy uczymy się czytać, opowieści, słowniki i słowa są raczej proste. Krótkie i proste historie wymagają niewielkich i prostych narzędzi. Kiedy jednak nasz poziom zaawansowania wzrasta i zapoznajemy się z dziełami Williama Shakespeare’a, potrzebujemy bardziej zaawansowanego, większego i bardziej wyrafinowanego zestawu narzędzi. Gdyby przedszkolanka przedstawiła swoim podopiecznym Hamleta, dzieci najprawdopodobniej by go nie zrozumiały. Jeżeli jednak dzieci otrzymają zestaw lektur stopniowo zwiększający ich zdolności czytelnicze, to po latach, kiedy będą w szkole średniej, będą w stanie przeczytać, zrozumieć i docenić Hamleta. Ta książka jest dla programistów, którzy są gotowi do zapoznania się z Hamletem w wersji PHP. Aby ta książka dostarczyła Ci niezbędną wiedzę, musisz mieć doświadczenie w programowaniu w PHP. Inne książki z tej serii, PHP5. Wprowadzenie Davida Sklara (Helion) i Learning PHP, MySQL, and JavaScript, 2nd Edition Robina Nixona (O’Reilly), zapewnią wiedzę potrzebną do rozpoczęcia przygody z PHP. Oczywiście mogłeś nauczyć się programować z dowolnej innej książki, kursu lub samouczka. Najważniejsze jest, abyś potrafił programować w PHP. Będziemy wykorzystywać PHP 5 i nie będziemy się zajmować starszymi wersjami, np. poprzednią — PHP 4 (PHP 4.4.9). Jest to spowodowane tym, że niemal wszystkie potrzebne do programowania obiektowego funkcjonalności zostały dodane dopiero w wersji PHP 5. Wstęp do średnio zaawansowanego i zaawansowanego programowania



21

Dlaczego programowanie zorientowane obiektowo? Mimo że programowanie obiektowe zostało wymyślone ponad czterdzieści lat temu, dopiero w ciągu ostatnich piętnastu lat zaczęło zyskiwać na popularności. W dużej mierze popularność ta zawdzięczana jest Javie, która zawiera wbudowane struktury obiektowe. Nowsze języki związane z internetem, takie jak JavaScript, ActionScript 3.0 i PHP 5, także wykorzystują już technologię obiektową. W 1998 r. Alexander Nakhimovsky i Tom Myers, profesorowie uniwersytetu Colgate, pokazali w książce JavaScript Objects (Wrox), że programowanie obiektowe jest możliwe w JavaScripcie. Programowanie obiektowe nie jest więc niczym nowym, nawet dla osób, które pracowały głównie w technologiach internetowych — można powiedzieć, że jest wypróbowanym i sprawdzonym sposobem programowania w językach zaprojektowanych do komunikacji z komputerem. Zrozumienie obiektowości jest istotne, gdyż wzorce projektowe oparte są na obiektach. Jeżeli więc jesteś programistą mającym doświadczenie w programowaniu w PHP 5, ale nie wykorzystywałeś jego możliwości obiektowych, poświęć szczególną uwagę wiadomościom zawartym w części pierwszej tej książki.

W jaki sposób ułatwić rozwiązywanie problemów? Programy komputerowe projektowane są po to, aby rozwiązywać problemy ludzi. Proces zwany programowaniem dynamicznym jest techniką pozwalającą na rozbicie problemu na mniejsze części. Każdy z mniejszych problemów rozwiązywany jest osobno, a następnie z powstałych części składne jest kompleksowe rozwiązanie. Za przykład posłuży nam planowanie wyprawy do Timbuktu (może się wydawać, że nie stanowi to problemu, ale spróbuj znaleźć lot do Timbuktu). Wyodrębnijmy następujące części:

1. Czy Timbuktu istnieje? (Tak/Nie). Odpowiedź = Tak. 2. Czy Timbuktu posiada lotnisko? (Tak/Nie). Odpowiedź = Tak, identyfikator lotniska = TOM. 3. Czy istnieją loty do Timbuktu? (Tak/Nie). Odpowiedź = Być może. Dostępne są loty

z Bamako i Mopti, ale w dniu 1 lipca 2012 kontrolę nad Timbuktu przejęli rebelianci islamscy, a loty zostały wstrzymane do odwołania.

4. Czy rebelianci nadal utrzymują kontrolę nad miastem? (Tak/Nie). Jeżeli odpowiedź = Tak, nie ma lotów. Jeżeli odpowiedź = Nie, loty mogą być dostępne.

5. Jeżeli loty są dostępne, czy Timbuktu jest bezpiecznym miejscem dla turystów i biznesmenów? (Tak/Nie). Odpowiedź = Nie.

6. Czy w moim kraju można otrzymać wizę Mali (kraju, w którym leży Timbuktu)? (Tak/Nie). Odpowiedź = Tak.

7. Czy wymagane są szczepienia? (Tak/Nie). Odpowiedź = Tak. Jak widzisz, dostanie się do Timbuktu i powrót są skomplikowane, ale na proste pytania z listy można udzielić odpowiedzi „tak” lub „nie”. Do listy można dodać jeszcze wiele pytań, ale na każde z nich możliwa jest odpowiedź binarna. Odpowiedź „być może” oznacza, że aby uzyskać jednoznaczną odpowiedź, konieczne jest zadanie większej liczby pytań.

22



Rozdział 1. PHP a programowanie obiektowe

Modularyzacja Proces rozbijania problemu na mniejsze podproblemy nazwany jest modularyzacją. Podobnie jak problem podróży do Timbuktu każdy problem można zmodularyzować do prostych kroków pozwalających otrzymać odpowiedź „tak” lub „nie”. Proces ten pokazano na rysunku 1.1.

Rysunek 1.1. Nawet najbardziej skomplikowane problemy mogą zostać rozbite na moduły

Patrząc na modularyzację, mógłbyś pomyśleć, że nie jest to skomplikowany proces. Miałbyś absolutną rację. Im bardziej złożony jest problem, tym bardziej opłacalna jest jego modularyzacja. Rozumowanie zorientowane obiektowo, mimo że nie jest skomplikowane, pozwala na uproszczenie rzeczy złożonych. Nawet najbardziej skomplikowane zadanie programistyczne może zostać rozwiązane za pomocą powyższej strategii „dziel i rządź”.

Klasy i obiekty Co zrobić z modułami, kiedy problem zostanie podzielony? Jak widzisz, problem może zostać podzielony na wiele mniej skomplikowanych podproblemów, ale do rozwiązania całościowego problemu potrzebny jest jeszcze sposób organizacji modułów i relacji pomiędzy nimi. Można spojrzeć na moduł jak na kolekcję powiązanych ze sobą funkcji. W programowaniu moduły nazywane są klasami. Klasa składa się z części nazywanych właściwościami i metodami. Właściwości są obiektami przechowującymi dane różnych typów: liczby, łańcuchy znaków, wartości null albo wartości logiczne. Dane te przechowywane są jako abstrakcyjne typy: zmienne, stałe i tablice. Metody są funkcjami wykonującymi działania na danych.

Klasy i obiekty



23

Zasada jednej odpowiedzialności O klasie można myśleć jako o kolekcji obiektów posiadających wspólne cechy. Nie oznacza to, że muszą one być takie same, chodzi o to, że odnoszą się do tego samego problemu zawartego w module. Pamiętając, że celem modułu jest rozwiązanie pewnej części bardziej skomplikowanego problemu, dochodzimy do pierwszej zasady programowania obiektowego: zasady jednej odpowiedzialności, mówiącej o tym, że klasa powinna posiadać tylko jedną odpowiedzialność. Nie oznacza to, że klasa nie może posiadać większej liczby odpowiedzialności, pamiętaj jednak, że rozbiliśmy skomplikowany problem na moduły, tak aby uzyskać problemy łatwe do rozwiązania. Ograniczając odpowiedzialność klasy, przypominamy sobie, po co rozbiliśmy problem, ale także zyskujemy na łatwości organizacji modułów. Przyjrzyjmy się klasie posiadającej jedną odpowiedzialność. Załóżmy, że dla klienta mamy stworzyć stronę internetową, a ponieważ strona ta będzie wyświetlana przy wykorzystaniu różnego rodzaju urządzeń, od komputerów stacjonarnych, po tablety i smartfony, potrzebny będzie sposób określenia, jaka przeglądarka i jakie urządzenie zostały użyte. PHP pozwala w łatwy sposób stworzyć klasę wykorzystującą wbudowaną tablicę $_SERVER oraz element HTTP_USER_AGENT. Klasa TellAll jest przykładem klasy o jednej odpowiedzialności — ma za zadanie wyświetlić nazwę przeglądarki wykorzystanej do otwarcia strony:

Otwarcie strony w przeglądarce Safari na iMacu spowoduje wyświetlenie następującego komunikatu: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_4) AppleWebKit/ 534.57.2 (KHTML, like Gecko) Version/5.1.7 Safari/534.57.2

Dla iPada i przeglądarki Opera Mini wynik będzie inny: Opera/9.80 (iPad; Opera Mini/7.0.2/28.2051;U;en) Presto/2.8.119 Version/11.10

Klasa reprezentuje moduł bardziej złożonej operacji, której jest częścią. Jak przystało na dobrą klasę, ma tylko jedno zadanie — sprawdzenie, z jakiej przeglądarki korzysta użytkownik.

Konstruktory w PHP Unikatową cechą PHP jest wykorzystanie funkcji __construct() jako konstruktora klasy. W większości języków programowania konstruktor przyjmuje taką samą nazwę jak klasa, ale wykorzystanie nazwy __construct() pozwala na wyeliminowanie wszelkich wątpliwości odnośnie do przeznaczenia funkcji. 24



Rozdział 1. PHP a programowanie obiektowe

Funkcja __construct() jest automatycznie uruchamiana podczas tworzenia instancji klasy. W klasie TellAll wynik jej działania jest wyświetlany na ekranie niezależnie od tego, czy sobie tego życzysz. Takie rozwiązanie jest poprawne dla celów demonstracyjnych, jednak w przypadku modułu inne moduły mogą chcieć wykorzystać informacje o urządzeniu i przeglądarce. Przekonasz się niedługo, że nie wszystkie klasy muszą zawierać konstruktor.

Klient jako klasa żądająca W klasie TellAll umieściłem na końcu kodu polecenie uruchamiające klasę. Z wyjątkiem klasy Client automatyczne uruchamianie nie jest jednak zalecane. Programując w PHP, najprawdopodobniej uruchamiałeś program za pośrednictwem znacznika formularza w HTML:

Potrafisz więc uruchamiać plik PHP ze źródła zewnętrznego. Pliki PHP zawierające klasy powinny być uruchamiane przez inne moduły (klasy), a nie przez siebie same. Kiedy będziemy zagłębiać się w temat wzorców projektowych, klasa Client będzie często się pojawiała. Client może pełnić różne role w projekcie, najważniejszą jest jednak wykonywanie klas składających się na wzorzec. Poniżej Client został wykorzystany w połączeniu z poprawioną wersją klasy TellAll. Nowa klasa różni się od pierwotnej klasy TellAll w kilku szczegółach, które powodują, że jest bardziej użyteczna dla pozostałych modułów i może zostać powtórnie wykorzystana w innych projektach. Klasa MobileSniffer rozpoczyna działanie od pobrania tych samych informacji o przeglądarce, jednak udostępnia je w o wiele bardziej użytecznej formie — dzięki właściwościom i metodom. Diagram UML (ang. Unified Modeling Language) pokazuje, że Client tworzy instancję (strzałka z przerywaną linią) klasy MobileSniffer. Rysunek 1.2 przedstawia prosty diagram klas pokazujący dwie powyższe klasy.

Rysunek 1.2. Klasa Client tworzy instancję klasy MobileSniffer i może korzystać z jej właściwości i metod

Gdyby obiekt klasy MobileSniffer został utworzony automatycznie, klasa Client miałaby mniejsze możliwości wykorzystania informacji pochodzących z MobileSniffer. Poniższy listing przedstawia treść klasy:

Ustawienie sposobu wyświetlania błędów w pliku php.ini Na co dzień pracuję w środowisku uniwersyteckim, gdzie administratorami są często studenci (o różnym poziomie wiedzy i kompetencji), którzy nadal uczą się swojego fachu. Często zapominają o ustawieniu wyświetlania błędów w pliku php.ini. W efekcie przyzwyczaiłem się do umieszczania następujących linii na początku mojego kodu: ini_set("display_errors","1"); ERROR_REPORTING(E_ALL);

Dla niektórych te dodatkowe linie kodu mogą być irytujące, jednak pozostawiłem je w klasie Client, aby przypomnieć, jak ważne jest wyświetlanie błędów podczas tworzenia aplikacji, w których niezwykle istotne są informacje pochodzące z klasy. Nauka programowania obiektowego i wzorców projektowych w dużej mierze opiera się na takich informacjach.

Aby skorzystać z klasy MobileSniffer, klasa Client tworzy jej instancję i wywołuje jej metody zgodnie z poniższym listingiem:

Wykorzystanie klasy Client pozwala na zwiększenie użyteczności klasy MobileSniffer. MobileSniffer nie musi uruchamiać sama siebie, a dzięki wartości zwracanej skorzystać z danych może inna klasa. Pobrane dane mogą być wykorzystane w dowolny sposób. W tym przypadku dane są formatowane i wyświetlane na ekranie — zgodnie z rysunkiem 1.3.

Rysunek 1.3. Klasa Client wykorzystuje klasę MobileSniffer, pobiera dane, a następnie wyświetla je na ekranie

Moglibyśmy sformatować dane już wewnątrz klasy MobileSniffer, ale wtedy nie byłaby ona tak elastyczna i użyteczna. Dane przekazane w takiej formie mogą zostać wykorzystane w dowolny sposób. Na przykład zamiast do wyświetlania komunikatu mogą zostać wykorzystane do wywołania pliku CSS przystosowanego do określonego urządzenia i przeglądarki. Gdyby dane zostały sformatowane już wewnątrz klasy MobileSniffer, ich wykorzystanie w klasie Client wymagałoby usunięcia formatowania. Pamiętaj, że jedną z najważniejszych cech wzorców projektowych jest możliwość powtórnego użycia klas w wielu projektach.

Klient jako klasa żądająca



27

Urządzenia mobilne W chwili pisania tej książki liczba urządzeń mobilnych ciągle rośnie i niektóre z nich mogą pozostać niewykryte przez kod PHP. Co więcej, samo wykrycie urządzenia może być niewystarczające, ponieważ niektóre z nich (np. iPad i iPad Mini) mogą mieć różne rozdzielczości i rozmiary ekranów. Jeżeli więc chcesz stworzyć aplikację PHP działającą na różnych urządzeniach, potrzebujesz modułu, który będzie mógł być łatwo aktualizowany bez konieczności modyfikowania reszty kodu aplikacji. Niezależnie od techniki wykorzystywanej do wykrywania urządzeń bądź gotowy do wprowadzania zmian. Aby przygotować aplikację dla nowych urządzeń, takich jak Microsoft Surface, możesz albo rozpocząć pracę od początku, albo wykorzystać moduł, który pozwoli na bezproblemową aktualizację istniejącej aplikacji.

Być może pomyślałeś „byłbym w stanie napisać lepszy algorytm do wykrywania urządzeń i przeglądarek”. Zapewne byłbyś w stanie i prawdopodobnie będziesz musiał ze względu na pojawiające się nowe urządzenia i przeglądarki, które także będą musiały zostać obsłużone. Jeżeli jednak zachowasz strukturę dwóch metod, findDevice() i findBrowser(), będziesz mógł wprowadzić dowolne zmiany, a program nadal będzie działał poprawnie. Wyobraź sobie o wiele większy i bardziej rozbudowany program i przemyśl zmiany, jakie będą musiały być wprowadzane. Jeżeli kiedykolwiek musiałeś modyfikować rozbudowany program, wiesz, że prosta zmiana może spowodować wiele problemów. Szukanie błędów staje się wtedy koszmarem. Zaletą programowania obiektowego i wzorców projektowych jest możliwość wprowadzania zmian w jednym module bez konsekwencji w innych częściach projektu.

Co z wydajnością? Prawie każdy programista chce, aby jego program działał z optymalną szybkością — w tym celu konieczne jest wykorzystanie najlepszych algorytmów. Na razie musimy jednak skupić się na innym rodzaju wydajności — ilości czasu potrzebnego do stworzenia i aktualizacji programu. Jeżeli program wykonuje tę samą operację sto milionów razy, nawet drobne optymalizacje mają znaczenie. Próba skrócenia o mikrosekundy operacji wykonywanej tylko raz jest natomiast stratą cennego czasu, podobnie jak konieczność sprawdzenia całej aplikacji z powodu zmian w paru linijkach kodu.

Szybkość tworzenia i modyfikacji rozwiązań Załóżmy, że dla klienta musisz zmodyfikować, a następnie serwisować program. Ustaliliście, jakie zmiany mają zostać wprowadzone, chcesz więc rzetelnie wykonać swoją pracę, jednocześnie poświęcając na nią możliwie jak najmniej czasu. Na przykład: klient urządza cotygodniowe wyprzedaże towaru, wymagające modyfikacji tekstu i grafiki. Jednym z rozwiązań będzie ustawienie cotygodniowej aktualizacji za pośrednictwem funkcji time() — wystarczy wtedy dodać ścieżkę do nowej grafiki i nowy tekst do bazy danych. Jeżeli dysponujesz tekstem i grafiką, możesz urządzić sobie urlop i pozwolić, aby PHP wykonało pracę za Ciebie. Byłby to naprawdę dobry układ i mógłbyś obsługiwać wielu klientów jednocześnie. Czy zdecydowałbyś się na system, który wymaga całościowej modyfikacji za każdym razem, kiedy konieczne jest wprowadzenie jakichś zmian? Prawdopodobnie nie. Wprowadzanie zmian w takim systemie byłoby bardzo pracochłonne i kosztowne. Jeżeli więc sprawność wprowadzania 28



Rozdział 1. PHP a programowanie obiektowe

zmian ma znaczenie, musisz brać pod uwagę zarówno szybkość wykonywania operacji, jak również szybkość wprowadzania zmian w kodzie. Na szybkość działania mają wpływ algorytmy, na szybkość tworzenia i modyfikacji kodu — wzorce projektowe.

Szybkość w zespole Kolejny problem związany z szybkością może pojawić się podczas współpracy w zespole. Podczas pracy nad większymi i bardziej zaawansowanymi projektami członkowie zespołu muszą ustalić oraz zrozumieć wspólny plan i cele prowadzące do efektywnego i wydajnego tworzenia oraz serwisowania programu. Programowanie obiektowe i wzorce projektowe stanowią między innymi wspólny język pozwalający na usprawnienie pracy zespołowej. Określenia fabryka, maszyna stanowa i obserwator oznaczają dla osób zaznajomionych z wzorcami projektowymi to samo. Co ważniejsze, wzorce projektowe pozwalają, aby poszczególni członkowie zespołu pracowali nad częściami systemu, które będą ze sobą współpracować. Pomyśl o linii montażowej w fabryce samochodów — każdy zespół składa inną część samochodu. Aby było to możliwe, potrzebny jest wzorzec oraz zrozumienie zależności pomiędzy poszczególnymi częściami. Dzięki temu każdy może zajmować się swoją częścią, wiedząc, że inni członkowie zespołu będą mogli z tej pracy skorzystać. Nie muszą znać szczegółów pracy innych osób. Muszą tylko wiedzieć, że wykonują ten sam plan.

Problemy z programowaniem sekwencyjnym i proceduralnym Wiele osób hołduje stwierdzeniu: „Nie naprawiaj tego, co nie jest zepsute” i być może zgodzisz się z nim, jeżeli rozwiązanie działa. Takie rozumowanie jest jednak antytezą rozwoju i innowacji. W końcu z jednego miejsca w drugie można przemieścić się piechotą. Aby jednak dostać się z jednego krańca kraju do drugiego, o wiele lepszym rozwiązaniem jest lot samolotem. Programowanie obiektowe jest tak dużym usprawnieniem dla programowania sekwencyjnego i proceduralnego jak loty dla podróżowania piechotą.

Programowanie sekwencyjne Większość programistów rozpoczyna programowanie od pisania linii kodu jedna pod drugą, tak aby ich seria utworzyła program. Poniższy kod stanowi poprawny program sekwencyjny w PHP:

Zmienne są abstrakcyjnymi typami danych, a arytmetyczny operator dodawania (+) łączy dwie zmienne, dając trzecią. Polecenie echo wyświetla wynik działania programu na ekranie. Dodawanie dwóch liczb jest bardzo prostą operacją i dopóki problemy są proste, możesz wykorzystywać proste rozwiązania. Problemy z programowaniem sekwencyjnym i proceduralnym



29

Programowanie proceduralne Kiedy programy tworzone przez programistów stawały się coraz bardziej skomplikowane, a poziom skomplikowania zadań przez nie wykonywanych zaczął rosnąć, sekwencje zmieniły się w tak zwany kod spaghetti. Polecenie GOTO pozwalało na przemieszczanie się w obrębie programu w celu wykonania sekwencji, łatwo więc było o zapętlenie. Wraz z programowaniem proceduralnym pojawiły się funkcje. Funkcja jest małym obiektem, który pozwala na wykonanie operacji za pomocą pojedynczego wywołania. Kod poniżej zawiera wersję proceduralną programu sekwencyjnego zademonstrowanego wyżej:

Funkcje (lub procedury) pozwalają programistom na grupowanie sekwencji w moduły, które mogą być wielokrotnie wykorzystywane w programie. Co więcej, dzięki parametrom programista może przekazywać do funkcji różne argumenty i może ona być używana z różnymi wartościami. Podobnie jak w przypadku programowania obiektowego programowanie proceduralne wykorzystuje podział na moduły i wielokrotne wykorzystanie tego samego kodu. Programowanie proceduralne nie daje jednak możliwości tworzenia klas, które mogą łączyć zadania programistyczne. Obiekty klas (instancje klas) mogą operować na własnych strukturach danych — w przypadku funkcji nie jest to możliwe. W efekcie programowanie proceduralne wymaga tworzenia długich sekwencji w celu wykonania dużych zadań. Praca w zespole także jest utrudniona, ponieważ członkowie zespołu nie mogą pracować nad różnymi, skorelowanymi klasami — w przeciwieństwie do programowania obiektowego.

Zapłać teraz lub zapłać potem Jakiś czas temu opublikowałem na blogu post zatytułowany „Brak czasu na programowanie obiektowe i wzorce projektowe” (http://bit.ly/108IFSF). Post powstał w odpowiedzi na stwierdzenia niektórych programistów, że nie mają czasu na wprowadzanie w swoich programach elementów programowania obiektowego i wzorców projektowych, nawet gdyby chcieli. Tłumaczyli, że projekt posiadał z góry narzucony termin wykonania i w celu jego dotrzymania musieli szybko sklecić rozwiązanie przy wykorzystaniu programowania sekwencyjnego i proceduralnego. Decydowali się na wykorzystanie klasy lub dwóch, tylko jeżeli mieli taką, która dokładnie odpowiadała kryteriom. Podczas nauki programowania i wzorców projektowych w PHP musisz pamiętać o kilku punktach, które zostały wyszczególnione w książce Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku:  projektowanie oprogramowania obiektowego jest trudne;  projektowanie oprogramowania obiektowego pozwalającego na jego powtórne wykorzy-

stanie jest jeszcze trudniejsze.

30



Rozdział 1. PHP a programowanie obiektowe

Zamiast patrzeć na te punkty jak na pretekst do niekorzystania z programowania obiektowego i wzorców projektowych, powinieneś widzieć w nich powody, dla których programowanie obiektowe i wzorce projektowe są tak wartościowe. Wiedza dodaje wartości do każdych umiejętności. Im wiedza jest trudniejsza do zdobycia, tym bardziej jest wartościowa. Nie spodziewaj się, że programowania obiektowego i wzorców projektowych nauczysz się szybko. Powinieneś zacząć wdrażać je w swoich programach powoli. W którymś momencie zaczniesz dostrzegać ich wartość. Z czasem nabierzesz umiejętności, zrozumiesz wzorce i w końcu trafisz na projekt, w którym będziesz mógł powtórnie wykorzystać kod z poprzednich projektów. W niedawnym projekcie zdecydowałem się na wykorzystanie wzorca Strategia. Projekt zawierał tabelę składającą się ze 105 pól, a klient żądał zestawu funkcjonalności. Dzięki wykorzystaniu wzorca Strategia każda ze strategii była klasą zawierającą algorytm rozwiązujący prosty problem — filtrowanie, aktualizację i usuwanie danych z bazy MySQL. Trochę czasu zajęło stworzenie klas, ale kiedy rozwiązanie zostało skonfigurowane, wprowadzanie zmian stało się bardzo proste (klienci zawsze chcą zmian!). Jakiś czas później zostałem poproszony o stworzenie podobnego projektu przy wykorzystaniu PHP i bazy MySQL. Zamiast rozpoczynać pracę od początku, wziąłem wzorzec strategii, zmieniłem algorytmy i w krótkim czasie miałem gotowe rozwiązanie. Otrzymałem taką samą zapłatę, ale dzięki temu, że wykorzystałem wzorzec, klient otrzymał lepsze oprogramowanie, niż gdybym pracował mniej sprytnie i dłużej. Czasami musimy pozbyć się starych nawyków i nauczyć się czegoś nowego. Obecnie większość programistów uczy się raczej tworzenia oprogramowania dla urządzeń mobilnych. Jeżeli nie będą się rozwijać, będą tracić zbyt wiele okazji, a z czasem ich umiejętności się zdezaktualizują. Wiemy, że będziemy musieli rozwijać nasze umiejętności, kiedy pojawi się nowa wersja PHP, nowa technologia lub przemysł obierze nowy kierunek. Programowanie obiektowe i wzorce projektowe zawierają koncepcje, które są ponad tymi zmianami, powodują, że jesteśmy lepszymi programistami, a nasi klienci otrzymują lepsze oprogramowanie. Wszystko zaczyna się od pierwszego kroku. Poświęcając czas teraz, w przyszłości nie będziesz musiał walczyć o dotrzymanie terminu projektu. Ponadto staniesz się lepszym programistą, a to samo w sobie jest doskonałym powodem do nauki programowania obiektowego i wzorców projektowych. I przede wszystkim: nauka programowania obiektowego i wzorców projektowych daje satysfakcję, że robimy coś dobrze.

Problemy z programowaniem sekwencyjnym i proceduralnym



31

32



Rozdział 1. PHP a programowanie obiektowe

ROZDZIAŁ 2.

Podstawowe zagadnienia programowania obiektowego

Do stworzenia prawdy potrzebne są dwa elementy — fakt i abstrakcja. — Rémy de Gourmont Ze swym cudownym dziedzictwem, tym wspaniałym krajem niepodległości, postępuj z rozwagą, bo jeżeli potkniemy się i przewrócimy, wolność i cywilizacja wszędzie obróci się w ruinę. — Henry Cabot Lodge Wszyscy dostają wszystko za darmo. Bogaci dziedziczą. I nie mam na myśli tylko dziedziczenia pieniędzy. Mówię o tym, co ludzie z klas wyższych i średnich uważają za oczywiste, czyli nepotyzmie i koneksjach. — Toni Morrison Nasza zwykła świadomość, którą nazywamy świadomością racjonalną, jest tylko jednym z typów świadomości, podczas gdy w oderwaniu od niej istnieją potencjalne formy zupełnie innej świadomości. — William James

Abstrakcja Jeżeli programowanie obiektowe jest dla Ciebie nowe, nie spodziewaj się, że wszystko od razu będzie jasne. Wraz z wykorzystywaniem programowania obiektowego pojawią się jednak chwile, kiedy powiesz: „Aha!”, a wszystkie informacje zaczną do siebie pasować. PHP posiada ważne funkcje obiektowe, które zaimplementowane są w niepowtarzalny sposób. Jeżeli znasz inne języki obiektowe, możesz być pewien, że PHP w pewnych miejscach różni się od nich — np. pozwala, aby stałe były częścią interfejsu. Zrozumienie abstrakcji jest bardzo ważne podczas nauki programowania obiektowego i wzorców projektowych. Zdolność myślenia abstrakcyjnego jest bardzo ważna zarówno w programowaniu obiektowym, jak i podczas pracy z wzorcami projektowymi. „Abstrakcja” w informatyce to pojęcie nieco inne od „abstrakcji”, którą posługujemy się w języku naturalnym. Na przykład słowo pies oznacza zjawisko posiadające zachowanie zbliżone do psa. Jeżeli ktoś powie „spójrz na psa”,

33

używa abstrakcji psa (słowa pies), aby określić instancję zwierzęcia posiadającego cechy psa. Wykorzystując słowa pies i kot, możemy rozróżnić gatunek, wskazując na wiele różnych psów i kotów. Jeżeli jednak użyjemy słowa pies w stosunku do kota, możemy zostać poprawieni. „To nie jest pies, to jest kot”. Mimo że abstrakcje są ogólne, są wystarczająco dokładne, aby określić instancję. W książce Object-Oriented Design with Applications, 3rd Edition (Addison-Wesley) Grady Booch, pionier programowania obiektowego, przedstawia następującą definicję abstrakcji: Abstrakcja oznacza najważniejsze cechy obiektu odróżniające go od wszystkich innych obiektów, dzięki czemu określa dokładne granice, odpowiednie do perspektywy patrzącego.

Abstrakcja jest ważna, ponieważ pozwala na grupowanie i klasyfikację obiektów. W pewnym sensie wszystkie klasy są abstrakcją zestawu operacji wykonywanych na danych. Myśląc o abstrakcji, pamiętaj: Abstrakcja jest głównym narzędziem pozwalającym na poradzenie sobie ze złożonością. Im bardziej złożony jest problem, tym bardziej do jego rozwiązania potrzebna jest abstrakcja.

To prawdziwy paradoks: abstrakcje to konkretne metody radzenia sobie ze złożonością. Grupujemy podobieństwa w rzeczywistości (abstrahujemy konkretne podobieństwo), aby łatwiej się z nimi pracowało. Zamiast powiedzieć „mój lojalny, śmiały, kudłaty, merdający ogonem, liżący twarze, mokronosy przyjaciel, który ma na imię BłądSkładni”, mogę powiedzieć „mój pies”.

Klasy abstrakcyjne Poza zwyczajnymi klasami PHP daje także możliwość tworzenia klas abstrakcyjnych. W programowaniu obiektowym i wzorcach projektowych klasy abstrakcyjne wykorzystywane są jako mechanizm organizacji struktury projektu. W przypadku klas abstrakcyjnych niemożliwe jest tworzenie ich instancji, można natomiast tworzyć obiekty klas konkretnych (takich, których instancje można tworzyć), które dziedziczą interfejsy i właściwości klasy abstrakcyjnej. Zanim rozwinę tę myśl, muszę wyjaśnić pojęcie interfejsu. Na pewno słyszałeś o interfejsach użytkownika, interfejsach sprzętowych i jeszcze paru innych, powiązanych z komputerami i oprogramowaniem. Kolejnym typem jest interfejs opisujący obiekt. Zacznę od przedstawienia prostej klasy zawierającej jedną metodę:

34



Rozdział 2. Podstawowe zagadnienia programowania obiektowego

Centralna część interfejsu składa się z sygnatur klasy, tworzonych przez operacje przez nią wykonywane (funkcje). Sygnatura składa się z nazwy operacji i parametrów. Sygnatury zawierają także typ wartości zwracanej, jednak ze względu na typowanie danych w PHP ten element sygnatury omówię w sekcji „Podpowiadanie typów: prawie typowanie”. Rysunek 2.1 przedstawia sygnaturę metody trick().

Rysunek 2.1. Sygnatura operacji w PHP

Wszystkie sygnatury klasy tworzą jej interfejs. Na przykład klasa OneTrick udostępnia jedną operację z jedną sygnaturą składającą się z nazwy trick() i jednego parametru — $whatever.

Właściwości i metody abstrakcyjne Aby zapisać metodę trick() jako abstrakcyjną, wystarczy zamieścić jej interfejs i nic poza tym: abstract public function trick($whatever);

Jeżeli klasa posiada przynajmniej jedną metodę abstrakcyjną, sama także musi być abstrakcyjna. Klasa abstrakcyjna może natomiast posiadać metody konkretne. Poniższa abstrakcyjna klasa zawiera metodę abstrakcyjną:

Oprócz metody abstrakcyjnej w klasie znajduje się zmienna, $storeHere. Zmienna będąca częścią klasy to właściwość. W niektórych przypadkach termin właściwości odnosi się zarówno do właściwości, jak i metod, jednak w większości przypadków odnosi się do zmiennych i stałych (dane abstrakcyjne), a termin metody odnosi się do funkcji (operacje wykonywane na danych). PHP nie obsługuje czegoś takiego jak właściwości abstrakcyjne. Można stworzyć właściwość bez przypisanej wartości i traktować ją jako abstrakcyjną, jednak w przeciwieństwie do metod abstrakcyjnych nie musisz z nich korzystać. Jeżeli w klasie abstrakcyjnej zadeklarujesz metodę abstrakcyjną, będziesz musiał zaimplementować tę metodę we wszystkich klasach dziedziczących. O metodach abstrakcyjnych można myśleć jak o kontrakcie, który obliguje podklasy do przestrzegania tych samych standardów. Na razie traktuj klasy abstrakcyjne jako kontener, który może zostać wypełniony dowolną zawartością i załadowany na statki, ciężarówki i pociągi. Kontenery, zupełnie jak klasy abstrakcyjne, są częścią większej struktury. Abstrakcja



35

Klasa dziedzicząca po klasie nazywana jest klasą potomną, a klasa abstrakcyjna (lub dowolna inna klasa, po której inna klasa dziedziczy) to klasa nadrzędna. Poniżej przedstawiona została implementacja klasy abstrakcyjnej OneTrickAbstract:

Klasa abstrakcyjna może być implementowana w dowolny sposób, o ile wykorzystane zostaną sygnatury i ustawiona zostanie odpowiednia widoczność. W tym przypadku sygnatura zawiera nazwę trick i pojedynczy parametr $whatever, a widoczność to public. Wszystkie poniższe implementacje metody trick() są poprawne: public function trick($whatever) { $echo $whatever; }

Lub: public function trick($whatever) { $half=$whatever/2; return $half; }

Lub: public function trick($whatever) { $this->storehere=25; $quarter=$whatever * 100; return ($quarter / $this->storehere); }

Pewnie zastanawiasz się nad sensem stosowania metod abstrakcyjnych, skoro można z nich korzystać tylko pod warunkiem zachowania tej samej widoczności i sygnatury. W części dotyczącej dziedziczenia (strona 13) dokładnie wyjaśni się znaczenie dziedziczenia interfejsów. Klasy abstrakcyjne oprócz metod abstrakcyjnych, które muszą być implementowane w klasach potomnych, mogą także zawierać dowolną liczbę metod i właściwości konkretnych. Klasa konkretna nie może natomiast zawierać metod abstrakcyjnych. PHP 5.4 wprowadza obsługę cech (ang. traits), które pozwalają na wielokrotne wykorzystanie kodu w przypadku, kiedy wielokrotne dziedziczenie nie jest dozwolone. Klasa może dziedziczyć po innej klasie i jednocześnie wykorzystywać cechy — jest to coś w rodzaju wielokrotnego dziedziczenia. W przykładach prezentowanych w tej książce cechy nie będą jednak stosowane — wspominam o nich na wypadek, gdybyś znalazł wykorzystujący wielokrotne dziedziczenie wzorzec projektowy z innego języka i chciałbyś przepisać go na PHP. 36



Rozdział 2. Podstawowe zagadnienia programowania obiektowego

Interfejsy Kolejnym elementem wykorzystywanym w programowaniu obiektowym i wzorcach projektowych jest interfejs. Tak jak klasy abstrakcyjne, również interfejsy zawierają metody abstrakcyjne. Natomiast w przeciwieństwie do klas abstrakcyjnych w interfejsie nie możesz umieszczać metod konkretnych ani zmiennych (wyjątkiem są stałe, które mogą być umieszczane w interfejsach — jest to jednak unikatowa właściwość PHP). Interfejsy są ważnym składnikiem strukturalnym w programowaniu obiektowym i wzorcach projektowych. Aby stworzyć interfejs, zamiast słowa class należy użyć słowa interface. Zgodnie z przyjętą konwencją nazewniczą nazwa interfejsu rozpoczynana jest od litery I lub i. W tej książce nazwy interfejsów rozpoczynane będą od wielkiej litery I, za nią będzie właściwa nazwa interfejsu, także rozpoczynająca się z wielkiej litery. Następnie deklarowane są metody abstrakcyjne, jednak bez wykorzystania słowa abstract. Poniższy kod prezentuje przykład prostego interfejsu, zawierającego trzy metody:

Aby zaimplementować interfejs, zamiast słowa extends wykorzystywane jest implement. Zauważ, że na listingu wykorzystana została funkcja include_once(), dzięki której interfejs widoczny jest w implementującej go klasie:

Abstrakcja



37

Uruchomiony program powinien zwrócić następujący wynik: Wiadomości z ostatniej chwili! Niebo się wali... Głosuj na senatora Parskacza! Zarabiasz 300 zł w swojej pracy dorywczej

Zauważ, że poza trzema metodami zaimplementowanymi na potrzeby interfejsu w klasie ImplementAlpha znajduje się także czwarta metoda — useMethods(). Oprócz metod z interfejsu klasy mogą zawierać dowolną liczbę metod i właściwości.

Interfejsy i stałe W interfejsach nie wolno umieszczać zmiennych, można jednak umieszczać stałe. Aby można było korzystać ze stałych, potrzebny jest operator rozróżnienia zasięgu (::). Znak podwójnego dwukropka może być wykorzystany do uzyskania dostępu do stałych zarówno w klasach, jak i poprzez implementację interfejsu. Poniższy kod prezentuje zasadę korzystania z operatora rozróżnienia zasięgu: $someVariable= InterfaceName::SOME_CONSTANT;

Na przykład ten interfejs zawiera zmienne wykorzystywane do połączenia z bazą MySQL:

Interfejs implementowany jest jak każdy inny. Wartości stałych mogą być przekazywane w implementacji za pomocą operatora rozróżnienia zasięgu:

Interfejs zawiera jedną metodę, testConnection(), jednak interfejsy mogą się też składać z samych stałych. Wartości przekazywane są do właściwości klasy (w przykładzie są to zmienne prywatne) przy wykorzystaniu nazwy interfejsu (IConnectInfo), operatora rozróżnienia zasięgu i nazwy zmiennej.

Klasy abstrakcyjne i interfejsy: dwa typy interfejsów Rozróżnienie klas abstrakcyjnych i interfejsów jest jednym z trudniejszych zadań podczas nauki programowania obiektowego i wzorców projektowych. Pamiętaj, że obie struktury posiadają interfejsy, czyli zestaw definicji metod. Zestaw sygnatur definiowany przez metody jest interfejsem klasy lub obiektu. Podczas pracy z wzorcami projektowymi możesz więc znaleźć informację o interfejsie odnoszącą się zarówno do interfejsu, jak i do klasy abstrakcyjnej. Odniesienie takie dotyczy zestawu sygnatur. Gamma i inni twierdzą, że „interfejs obiektu określa kompletny zestaw odwołań, które mogą zostać przesłane do obiektu”. Oznacza to, że jeżeli klasa Client zna interfejs obiektu, wie także, jakie dane i w jaki sposób może z niego uzyskać. O ile więc zarówno klasy abstrakcyjne, jak i interfejsy mają swoje interfejsy, w klasie abstrakcyjnej możemy również definiować metody konkretne (operacje) i właściwości. Zrozumienie wzorców projektowych zależy w dużej mierze od zrozumienia terminu interfejs, odnoszącego się do zestawu sygnatur obiektu (klasy).

Podpowiadanie typów: prawie typowanie Ważnym elementem struktury programowania obiektowego i wzorców projektowych jest typowanie danych do interfejsu zamiast do jego implementacji. Oznacza to, że dostęp do danych uzyskiwany jest poprzez klasę nadrzędną — zazwyczaj klasę abstrakcyjną lub interfejs (w tym kontekście interfejs może oznaczać klasę abstrakcyjną lub interfejs). Format podpowiadania typów jest następujący: function doWork(TypeHint $someVar)...

Podpowiedź typu musi być klasą lub interfejsem. W przypadku wzorców projektowych preferowane są klasy abstrakcyjne lub interfejsy, ponieważ nie wymuszają one typu implementacji, a jedynie strukturę. Poniższy przykład przedstawia interfejs zaimplementowany na dwa sposoby oraz klasę, która wykorzystuje go w podpowiadaniu typów, tworząc luźne, ale jasne przypisanie: Interfejs:

Abstrakcja



39

Implementacja FruitStore:

Implementacja CitrusStore:

function oranges() { return "CitrusStore mówi: Mamy owoce cytrusowe.
"; }

Obiekty wykorzystujący podpowiadanie typów: //UseProducts.php

40



Rozdział 2. Podstawowe zagadnienia programowania obiektowego

Wywołanie klasy UseProducts da następujący wynik: FruitStore mówi: Mamy jabłka. FruitStore mówi: Nie mamy owoców cytrusowych. CitrusStore mówi: Nie sprzedajemy jabłek. CitrusStore mówi: Mamy owoce cytrusowe.

To, co widzisz na ekranie, to różne implementacje interfejsu IProduct. Należy zauważyć, że podpowiedź typu w metodzie doInterface() odpowiada obu klasom implementującym interfejs — FruitStore i CitrusStore są rozpoznawane jako IProduct. W praktyce sprawdzanie typu pozwala upewnić się, że obiekt (klasa) wykorzystany w połączeniu z daną metodą będzie implementował interfejs podany w podpowiadaniu typów. Co więcej, jeżeli przy podpowiadaniu typu wykorzystamy interfejs (klasę abstrakcyjną lub interfejs), przypisanie jest mniej rygorystyczne — przypisany jest interfejs, a nie konkretna implementacja. Kiedy Twój program będzie rozbudowywany, będziesz mógł wprowadzać zmiany, dbając tylko o poprawną implementację interfejsu, nie będziesz musiał zagłębiać się w konkretne implementacje. Typy skalarne, takie jak string czy int, nie mogą być wykorzystywane przy podpowiadaniu typów, możesz natomiast korzystać z tablic, interfejsów (jak w poprzednim przykładzie) i klas. Mimo że PHP nie oferuje typowania, jak w przypadku niektórych innych języków, podpowiadanie typów pełni kluczową rolę w programowaniu obiektowym i wzorcach projektowych.

Enkapsulacja Czytając o enkapsulacji, często spotkasz się z określeniem ukrywanie informacji. To określenie nie jest dokładne, ale kiedy zrozumiesz, czym jest enkapsulacja, stanie się bardziej jasne. Wyjaśniania pojęcia enkapsulacji nie powinno się rozpoczynać od zjawiska ukrywania informacji. Lepiej zacząć od porównania z podziałem na części; Grady Booch opisuje to następująco: Enkapsulacja to proces podzielenia na części elementów abstrakcji, które określają jej strukturę i zachowanie; enkapsulacja oddziela umowny interfejs abstrakcji od jej implementacji.

Kiedy podzieli się duży problem na moduły będące częściami problemu, enkapsulacja pozwala na podzielenie mniejszych abstrakcji na poszczególne elementy.

Enkapsulacja w życiu codziennym Z enkapsulacją stykasz się każdego dnia. Weźmy na przykład przejażdżkę samochodem. Samochód składa się z wielu obiektów, a sposób działania większości z tych części nie jest Ci znany. Zapłon uruchamia silnik, ale możesz nie wiedzieć, jak zbudowany jest rozrusznik zasilany z akumulatora, jak działa silnik spalinowy i jak zbudowana jest instalacja elektryczna w samochodzie. Wkładasz tylko kluczyk do stacyjki i obracasz go, aby uruchomić silnik. Szczegóły budowy samochodu są przed Tobą ukryte i możesz uzyskać do nich dostęp tylko w określony sposób. Pracujesz z systemem enkapsulowanym. Nie musisz wiedzieć, jak działają poszczególne części, wystarczy, że wiesz, jak uzyskać dostęp do mechanizmów sterujących — myśl o nich jak o interfejsie użytkownika (UI) Twojego samochodu.

Enkapsulacja



41

Załóżmy, że jedziesz drogą z opuszczoną szybą, obok podjeżdża inny samochód, a pasażer tego samochodu wyciąga rękę i próbuje złapać Twoją kierownicę i kierować Twoim samochodem. Jest to złamanie enkapsulacji. Nie chcesz takich sytuacji, a więc podnosisz szybę, aby nikt spoza samochodu nie miał dostępu do enkapsulowanego kokonu samochodu. W programowaniu to enkapsulacja czyni obiekt obiektem. Obiekt ma pewne cechy, które sprawiają, że dostęp do jego funkcjonalności jest kontrolowany przez strukturę programu — tak jak struktura samochodu kontroluje dostęp do jego części. Klasa jest enkapsulowana poprzez ograniczenie dostępu do jej metod i właściwości. Nie chcesz, aby właściwości klasy były kontrolowane i zmieniane z zewnątrz poza jasno określonymi ścieżkami — tak jak nie chciałbyś, aby ktoś przejął kontrolę nad Twoją kierownicą. Kiedy więc spotkasz się z określeniem ukrywanie informacji w kontekście enkapsulacji, oznacza to, że szczegóły modułu zostały ukryte, tak aby manipulacje dostępne były za pośrednictwem odpowiednich kanałów dostępu, a nie poprzez szczegóły modułu.

Zapewnienie enkapsulacji poprzez widoczność W PHP termin widoczność określa dostęp do właściwości klasy (w innych językach wykorzystywany jest termin dostęp, a dla typów dostępu modyfikator dostępu). PHP, podobnie jak inne języki obiektowe, udostępnia trzy rodzaje widoczności: private, protected i public. Widoczność określa sposób, w jaki program może być enkapsulowany, oraz sposób dostępu do niego.

Private Najprostszym sposobem na enkapsulowanie elementu programu jest ustawienie go jako private. Oznacza to, że dostęp do właściwości może być uzyskany tylko z tej samej klasy — jest ona widoczna tylko dla elementów tej klasy. Przyjrzyjmy się poniższej klasie:

Konstruktor uruchamia klasę natychmiast po utworzeniu jej instancji. Ponieważ konstruktor jest częścią klasy PrivateVis, ma dostęp do wszystkich prywatnych właściwości i metod. Aby odnieść się do właściwości lub metody z tej samej klasy w danym obiekcie, konieczne jest wykorzystanie słowa kluczowego $this. Metoda prywatna secret() może uzyskać dostęp do właściwości (zmiennej) prywatnej $money, ponieważ należą do tej samej klasy. Obiekty będące na zewnątrz klasy będą mogły zobaczyć jedynie wynik działania metody secret() — nie będą mogły w żaden sposób wpływać na stan metody i właściwości wewnątrz klasy. 42



Rozdział 2. Podstawowe zagadnienia programowania obiektowego

Inny obiekt uzyskuje dostęp do prywatnych właściwości i metod, tworząc instancję klasy poprzez publiczny konstruktor (funkcję __construct).

Protected Widoczność private pozwala na uzyskanie dostępu tylko elementom należącym do tej samej klasy, natomiast protected pozwala na uzyskanie dostępu zarówno elementom należącym do tej klasy, jak i elementom klas pochodnych. Widoczność ta może być nadawana metodom konkretnym i abstrakcyjnym. Poniżej prezentujemy przykład wykorzystania metody abstrakcyjnej i jej implementacji w połączeniu z widocznością protected:

Kiedy klasa potomna dziedziczy po klasie abstrakcyjnej zawierającej metody chronione, musi je zaimplementować przed ich wykorzystaniem. Klasa potomna może korzystać z konkretnych metod chronionych bez konieczności ich implementacji:

Zwróć uwagę, że metoda abstrakcyjna countMoney() korzysta z dziedziczonej metody setHourly(). Wszystkie dziedziczone właściwości i metody wymagają wykorzystania słowa kluczowego $this. Dostęp do właściwości chronionych jest inicjowany poprzez publiczny konstruktor. Widoczność protected nie jest tak enkapsulująca jak widoczność private, może jednak enkapsulować większe struktury programu. Klasa nadrzędna, abstrakcyjna lub konkretna, i jej klasa podrzędna tworzą większą strukturę.

Enkapsulacja



43

Public Dostęp do obiektu enkapsulowanego można uzyskać poprzez widoczność public. Aby klasa była użyteczna, przynajmniej część jej metod musi być publiczna, nawet jeżeli będzie to tylko konstruktor. Oczywiście wszystkie funkcje konstruktora są publiczne. Jeżeli więc program zawiera konstruktor, dostęp z zewnętrznej klasy można uzyskać, tworząc obiekt klasy. Na przykład poniższy kod obrazuje, w jaki sposób metody i właściwości prywatne mogą być wykorzystywane za pośrednictwem pojedynczej metody publicznej:

Często w celu udostępnienia możliwości komunikacji z obiektem bez konieczności automatycznego uruchamiania jej mechanizmów w konstruktorze tworzone są metody i właściwości publiczne. W klasie PublicVis właściwość $password i metoda openSesame() są prywatne. Publiczna jest natomiast metoda unlock() — ponieważ jest częścią klasy PublicVis, ma dostęp do prywatnych elementów klasy.

Funkcje ustawiające i pobierające W programowaniu obiektowym i wzorcach projektowych w celu zachowania enkapsulacji i jednoczesnego zapewnienia dostępności zalecane jest wykorzystanie metod ustawiających i pobierających (zwanych także odpowiednio mutatorami i akcesorami). Zamiast bezpośrednio pobierać i modyfikować dane zapisane we właściwościach, można wykorzystać funkcje ustawiające i pobierające. Powinny być jednak wykorzystywane z rozwagą — przesada może zniweczyć enkapsulację. Poniższy kod przedstawia przykład wykorzystania funkcji ustawiających i pobierających w PHP:

Funkcje getter/setter są prywatne, więc enkapsulacja jest zachowana. Co więcej, w tej implementacji wartość ustawiana jest wewnątrz klasy, dzięki czemu klasa funkcjonuje jako duży magazyn danych. W książce Holub on Patterns (Apress) Allen Holub sugeruje podczas pracy z danymi w systemach obiektowych: Nie proś o informacje, których potrzebujesz do pracy; poproś obiekt dysponujący informacjami, aby wykonał pracę za Ciebie.

W przykładzie klasy GetSet linia, w której tworzona jest instancja: $worker=new GetSet();

właśnie to robi. Szczegóły implementacji nie są widoczne na zewnątrz. Klasa GetSet nie jest jednak specjalnie użyteczna, ponieważ jedynym sposobem na ustawienie wartości jest wpisanie jej w kodzie aplikacji. Jednym z zadań wzorców projektowych jest ustalenie komunikacji pomiędzy obiektami. Część rozwiązań, które są wykorzystywane w programowaniu obiektowym, błędnie wykorzystuje mutatory i akcesory, psując enkapsulację. Poniższe wyjaśnienie, pochodzące z artykułu „Pojedyncza odpowiedzialność w odniesieniu do metod” Davida Chelimsky’ego (http://tinyurl.com/9256gey), opisuje różnicę między programowaniem obiektowym a programowaniem proceduralnym i powinno wyjaśnić rolę komunikacji w programowaniu obiektowym: W programowaniu proceduralnym proces jest wyrażany w jednym miejscu i zawiera serię instrukcji wykonywanych w określonej kolejności. W programowaniu obiektowym natomiast proces składa się z wiadomości przesyłanych pomiędzy obiektami. Jeden obiekt przesyła wiadomość do innego, który wykonuje część procesu i następnie przysyła nową wiadomość do innego obiektu, który obsługuje część procesu itd. Proces modyfikowany jest raczej poprzez reorganizację kolejności przesyłanych wiadomości niż zmianę procedury.

Zachowanie enkapsulacji i jednoczesne zapewnienie komunikacji pomiędzy obiektami jest celem wzorców projektowych. Opracowanie takiej metody dostępu, która nie zaburzy enkapsulacji, może być trudne, a wzorce projektowe stają się swego rodzaju ściągą, pozwalającą na zbudowanie struktury programu.

Enkapsulacja



45

Dziedziczenie Zagadnienie dziedziczenia jest w gruncie rzeczy proste — klasa dziedzicząca przejmuje wszystkie właściwości i metody klasy nadrzędnej. Dzięki temu programiści mogą tworzyć nowe klasy, które rozwijają funkcjonalność klas już istniejących. Na przykład: jeżeli masz klasę FurryPets (kudłate zwierzaki), możesz rozszerzyć ją, tworząc klasy Dogs (psy) i Cats (koty). Klasy potomne odziedziczą wspólne cechy klasy nadrzędnej i otrzymają nowe, rozróżniające poszczególne gatunki. Poniższy kod prezentuje prosty przykład: FurryPets.php

Dogs.php

Cats.php

Client.php

Klasa Client wykonuje wywołania, a konstruktory klas Dogs i Cats wyświetlają następujący wynik: Psy chodzą na czterech łapach Hau, hau Wrrrrr Koty chodzą na czterech łapach Miau, purrr Przejdę się tylko po tej klawiaturze..

Metoda fourlegs() generuje taki sam wynik dla obu obiektów; wynik funkcji makeSound() uzależniony jest od przekazanego parametru. Każda klasa ma też własną unikatową metodę — guardsHouse() i ownsHouse(). Dziedziczenie pozwala na ustrukturyzowanie klas tworzących program. Żeby jednak powiązanie między klasami zachowało elastyczność, dziedziczone są przeważnie klasy abstrakcyjne, a samo dziedziczenie jest płytkie — obejmuje pojedynczy poziom klas potomnych. Jeżeli w programach występuje wiele poziomów dziedziczenia klas konkretnych, to nawet najmniejsza zmiana w klasie nadrzędnej może siać spustoszenie w klasach podrzędnych.

Polimorfizm Określenie „polimorfizm” odnosi się dosłownie do wielu form, jednak to mało przydatne stwierdzenie, jeżeli nie zrozumiesz jego znaczenia w odniesieniu do programowania obiektowego. Prawdziwą wartością polimorfizmu jest to, że obiekty implementujące ten sam interfejs mogą być wywoływane w różnych celach. W dużej i złożonej strukturze (rozbudowany program) zmiany mogą spowodować poważne problemy, jeżeli nie będzie wspólnego interfejsu (z klasy nadrzędnej lub interfejsu). Na przykład poniższy program składa się z dwóch klas dzielących wspólny interfejs. Implementacje interfejsu są bardzo różne: Polimorfizm



47



48



Rozdział 2. Podstawowe zagadnienia programowania obiektowego

Implementacje interfejsu ISpeed w klasach Jet i Car bardzo się od siebie różnią. Wprowadzając jednak zmiany w programie o takiej strukturze, można korzystać z metod interfejsu bez obawy o to, czy program przestanie działać.

Jedna nazwa, wiele implementacji Najprostszym sposobem wykorzystania polimorfizmu są różne implementacje interfejsu, tak jak w przykładzie Poly.php. W klasie Jet każda zaimplementowana metoda zwraca wartość prymitywną, a w klasie Car wartość prymitywna zapisywana jest najpierw do zmiennej i to zmienna jest zwracana. Obie klasy wykorzystują ten sam interfejs. Wykorzystując polimorfizm w klasach i interfejsach PHP, należy pamiętać, że sygnatura funkcji nie uwzględnia zwracanego przez nią typu. Na przykład sygnatura funkcji w języku C# zawierałaby zarówno typ zwracany, jak i informację o tym, czy oczekiwane jest zwrócenie wartości. Poniżej znajduje się deklaracja interfejsu napisana w języku C#: //C# Interface interface IFace { string stringMethod(); int numMethod(int intProp); void noReturnMethod(); }

W C# nie jest konieczne używanie słowa kluczowego function — wystarczy sygnatura składająca się z typu zwracanego, nazwy i parametrów (jeżeli są wymagane). Zatem pierwsza metoda — stringMethod() — wymaga zwrócenia łańcucha znaków, a każda implementacja musi zawierać słowo kluczowe return. Podobnie druga metoda — NumMethod() — wymaga zwrócenia liczby całkowitej; do metody musi także zostać przekazana liczba całkowita. Także ta sygnatura wymaga, aby implementacje zawierały słowo kluczowe return. W przypadku trzeciej metody — noReturnMethod() — typ zwracany to void. Oznacza to, że zwrócenie wartości nie jest wymagane — co więcej, zwrócenie wartości przez metodę implementującą spowodowałoby błąd. W PHP sygnatura nie zawiera typu zwracanego, dlatego pomieszanie implementacji zwracających i niezwracających wartości może powodować problemy. Zaletą polimorfizmu jest to, że mimo iż formy mogą być różne, interfejs pozwoli odpowiednio pokierować implementacją. Im więcej informacji zostanie zawartych w interfejsie — takich jak zachowanie (czy powinny zostać zwrócone dane) czy typ zwracanych danych — tym łatwiej uzyskać różne, współpracujące ze sobą formy. W przykładzie Poly.php wszystkie metody zawierały liczbę całkowitą i zwrócenie wartości. Dopóki więc sygnatura w PHP nie zostanie wzmocniona, należy dbać o to podczas planowania i implementacji. Można to zrobić, korzystając z komentarzy w kodzie interfejsu, na przykład: //Interfejs PHP interface IFace { //@zwraca string function stringMethod(); //@zwraca integer i przyjmuje integer jako parametr function numMethod($intProp); //nie zwraca wartości function noReturnMethod(); }

Jeżeli w interfejsach (oraz klasach abstrakcyjnych) wykorzystasz za pośrednictwem komentarzy rozszerzoną wirtualną sygnaturę, możesz skorzystać z zalet silnie typowanego języka.

Polimorfizm



49

Polimorfizm wbudowany we wzorcach projektowych Kiedy będziesz przyglądał się wzorcom projektowym z tej książki i z innych źródeł, powinieneś wiedzieć, że sporo polimorfizmu wbudowane jest we wzorce projektowe. Na przykład wzorzec Strategia zawiera interfejs Strategia i różne algorytmy w jego implementacjach. Strategia deklaruje wspólny interfejs dla różnych algorytmów wywodzących się ze Strategii. Na przykład program może zawierać różne implementacje interfejsu dla różnych akcji wykonywanych na bazie danych, takich jak aktualizacja, wstawienie czy pobranie danych. Dzięki wspólnemu interfejsowi nowe algorytmy dla specjalistycznych akcji mogą być dodawane bez obawy o działanie programu obsługującego bazę MySQL.

Tylko spokojnie Ten rozdział zawiera większość głównych zagadnień programowania obiektowego. Kiedy jednak będziesz zapoznawał się z kolejnymi rozdziałami i wzorcami projektowymi, przekonasz się, że zagadnienia te będą przytaczane w odniesieniu do konkretnego elementu wzorca lub implementacji wzorca w PHP. Pamiętaj więc, że jeżeli coś nie jest do końca jasne, powinno się wyjaśnić w dalszej części książki. Rozdział 3. zawiera omówienie głównych zagadnień dotyczących wzorców projektowych bazujących na zagadnieniach programowania obiektowego.

Korzystaj z karteczek przyklejanych na stronach We wszystkich swoich książkach dotyczących projektowania obiektowego i wzorców projektowych mam ponaklejane kolorowe karteczki. Są to karteczki indeksujące — mogą też nazywać się inaczej; niezależnie jednak od nazwy powinieneś się w nie zaopatrzyć i zaznaczać miejsca, do których powinieneś wrócić, aby przeczytać je jeszcze raz. Jeżeli do czytania wykorzystujesz tablet, np. iPada lub Kindle, skorzystaj z zakładek elektronicznych. Zależnie od czytnika (i oprogramowania) mechanizmy zakładek mogą być różne. Z podkreślania korzystaj ostrożnie, ja w niektórych książkach podkreśliłem wszystko, co jest równoznaczne z niepodkreśleniem niczego. Niektóre dokumenty na moim tablecie mogę podkreślać elektronicznie, ale w takim przypadku także trzeba to robić z rozwagą.

50



Rozdział 2. Podstawowe zagadnienia programowania obiektowego

ROZDZIAŁ 3.

Podstawowe zagadnienia wzorców projektowych

Duże organizacje w luźnej organizacji. Nie, byłoby to tak prawdziwe jak powiedzenie, że organizacja to zawsze dezorganizacja. — Gilbert K. Chesterton Jeżeli dysponujesz tylko młotkiem, każdy problem będziesz postrzegał jako gwóźdź. — Abraham Maslow Ponieważ rzeczy są, jakie są, nie pozostaną, jakie są. — Bertolt Brecht

Wprowadzenie do MVC W latach 70., kiedy interfejsy użytkownika (UI) były jeszcze mało skomplikowane, programista zdecydował się na odseparowanie głównych elementów wymaganych przez graficzny interfejs użytkownika. Każda z części otrzymała swoje zadanie i każda komunikowała się z pozostałymi częściami. Części były grupowane w obiekty domenowe i obiekty prezentacyjne. Obiekty domenowe zajmowały się modelowaniem percepcji świata rzeczywistego, natomiast obiekty prezentacyjne stanowiły części widoczną na ekranie. Części zostały rozdzielone na model, widok i kontroler — tak powstał wzorzec projektowy Model-View-Control (MVC). Element domenowy w MVC to model. Model odpowiedzialny jest za część szeroko określaną jako dane, część biznesowa lub logika aplikacji. Rozważmy przykład termostatu. Termostat przechowuje wartości reprezentujące temperaturę aktualną oraz wartości, przy których termostat jest włączany i wyłączany. W termostacie zawarte są więc informacje, które można zapisać za pomocą zmiennych: $a = aktualna temperatura $b = włącz grzałkę $c = wyłącz grzałkę

W danej chwili wartości mogą wynosić: $a=18; $b=15; $c=21;

51

Te wartości są generowane poprzez ustawienia kontrolera i przez termometr odczytujący aktualną temperaturę. Niezależnie od tego, gdzie i jak generowane są wartości, jest to część biznesowa modelu. Część prezentacyjna MVC składa się z dwóch części: widoku i kontrolera. W przykładzie z termostatem widok będzie stanowiło okienko prezentujące aktualną temperaturę i ustawienia. Model zapewnia informacje o temperaturze, a kontroler temperaturę włączenia i wyłączenia przekazywaną do widoku. Kontroler to urządzenie pozwalające na modyfikowanie temperatury włączenia i wyłączenia grzałki (widok wyświetla kontroler). Rysunek 3.1 przedstawia termostat jako MVC.

Rysunek 3.1. Termostat jako MVC

Termostat wiszący na ścianie i jego wirtualna reprezentacja różnią się sposobem komunikacji widoku z kontrolerem i sposobem, w jaki obsługiwane są dane pochodzące od użytkownika. Pamiętaj, że w przypadku programu komputerowego interfejs jest częścią widoku, który pozwala także na wprowadzanie zmian. Widok przekazuje następnie dane wprowadzone przez użytkownika do kontrolera, który z kolei przesyła je dalej do modelu. Rysunek 3.2 przedstawia ten sam obiekt na diagramie klas.

Rysunek 3.2. Diagram klas MVC

52



Rozdział 3. Podstawowe zagadnienia wzorców projektowych

MVC ma równie wiele odmian co implementacji. Dzięki temu, że jest prosty i użyteczny, był stosowany mnóstwo razy — poprawnie i niepoprawnie. Jest jednak przede wszystkim strukturą, która pozwala na luźne połączenie jej części. Załóżmy na przykład, że chcemy zmienić widok z analogowego na cyfrowy. Jest to proste zadanie, ponieważ kontroler i model są osobnymi encjami i nie ma dla nich znaczenia, co wyświetla widok. Podobnie jeżeli kontroler zmieni ustawienie ze stopni Fahrenheita na stopnie Celsjusza, a dane są przekierowywane do modelu, nie będzie miało znaczenia, jaki format temperatury jest wykorzystywany. MVC ma większą wartość jako demonstracja luźnego łączenia niż jako funkcjonalność. Wyodrębniając poszczególne elementy (uczestników), MVC pozwala na uzyskanie elastyczności potrzebnej przy dużych projektach. Im większy jest program, tym bardziej potrzebna jest elastyczna modułowość zapewniana przez MVC.

Podstawowe zasady wzorców projektowych MVC jest bardzo ważnym punktem w historii wzorców projektowych, jest jednak tylko początkiem. Wykorzystanie, złe wykorzystanie i nadmierne wykorzystanie MVC jest dobrze udokumentowane. Przesada w jego wykorzystaniu jest jak próba budowy domu tylko jednym narzędziem — młotkiem — wykorzystanym do piłowania, mierzenia i wiercenia. W publikacji Wzorce projektowe: elementy obiektowe wielokrotnego użytku Ericha Gammy, Richarda Helma, Ralpha Johnsona i Johna Vlissidesa (znanych także jako Banda Czterech — The Gang of Four lub GoF) MVC wykorzystywany jest do rozpoczęcia dyskusji nad narzędziami składającymi się na wzorce projektowe. Pozwala na rozdzielenie widoków i modeli poprzez ustanowienie między nimi protokołu subskrypcji/notyfikacji. GoF pokazuje wiele fundamentalnych wzorców projektowych, które składają się na MVC, ale także znacznie szerszy zakres wzorców, które mogą znacznie ułatwić pracę programisty, a nie są związane z MVC. Zamiast więc patrzyć na wzorce w powiązaniu z MVC, powinniśmy analizować je w oderwaniu od niego. Musimy poznać generalne zasady rządzące wzorcami projektowymi i każdy wzorzec z osobna.

Dlaczego Banda Czterech? Nie jestem miłośnikiem żargonu i idiomów, a określenie „Banda Czterech” w odniesieniu do Ericha Gammy, Richarda Helma, Ralpha Johnsona i Johna Vlissidesa — autorów publikacji Wzorce projektowe: elementy obiektowe wielokrotnego użytku — może się wydać nie na miejscu. Przydomek „Banda Czworga” został pierwotnie nadany czworgu przywódcom Chińskiej Partii Komunistycznej (byli to Jiang Qing, Zhang Chunqiao, Yao Wenyuan i Wang Hongwen), wyrzuconym z partii po śmierci Mao Zedonga. Odpowiedzialni byli oni za rewolucję kulturalną, która prawie zniszczyła Chiny (istnieje nawet zespół rockowy o nazwie Gang of Four, który swoją nazwę wywodzi od chińskich przywódców). Jednak określenie „Banda Czterech” i skrót GoF przyjęły się raczej z powodu liczebności autorów książki oraz jej długiego tytułu. Nazwa nie jest dwuznaczna, ponieważ wiadomo, do czego się odnosi. Stanowi wygodny skrót, który znany jest w środowisku związanym z programowaniem obiektowym i wzorcami projektowymi — nic więcej.

Podstawowe zasady wzorców projektowych



53

Pierwsza zasada wzorców projektowych Pierwsza zasada brzmi: wykorzystuj interfejsy, nie implementacje, co w przypadku PHP jest nieco skomplikowane. Najprościej mówiąc, należy ustawiać zmienne jako instancje klasy abstrakcyjnej lub typy interfejsu zamiast ich konkretnej implementacji. Korzystając z interfejsu, niwelujesz powiązanie pomiędzy rozwiązaniem a implementacją, a dzięki temu możesz na przykład podmienić skomplikowaną implementację bazy danych na prostą zaślepkę do celów testowych. W językach, które przy deklaracji zmiennych wykorzystują typowanie danych, wystarczy wpisać zamiast klasy konkretnej nazwę interfejsu jako typ zmiennej. Na przykład w silnie typowanym języku mogłoby to wyglądać następująco: Interfejs IAlpha Nazwa interfejsu Klasa AlphaA Implementuje IAlpha; AlphaA jest także typem IAlpha Zmienna useAlpha Typ deklarowany jako IAlpha useAlpha Stworzenie instancji new AlphaA() W PHP nie możesz zadeklarować zmiennej, aby miała typ abstrakcyjnego rodzica (klasy abstrakcyjnej lub interfejsu), ponieważ w PHP nie można deklarować typów bez tworzenia instancji klasy. Nie można także stworzyć instancji nadrzędnej klasy abstrakcyjnej. Zmienna może dostać typ podczas tworzenia instancji klasy, zgodnie z poniższym: $useAlpha = new AlphaA();

Zmienna $useAlpha jest instancją klasy AlphaA i tym samym jest takiego właśnie typu. Zatem $useAlpha jest typu AlphaA, czyli przyjmuje typ klasy konkretnej. Instancja klasy konkretnej jest jednak także typu klasy nadrzędnej. Warto to powtórzyć: Instancja obiektu jest typu klasy, której jest instancją, oraz typu klasy nadrzędnej w stosunku do tej klasy.

Jak można to osiągnąć bez przechowywania typów danych? W PHP można korzystać z interfejsu dzięki podpowiadaniu typów.

Wykorzystanie typów interfejsowych przy podpowiadaniu typów W rozdziale 2., w podrozdziale „Podpowiadanie typów: prawie typowanie”, przedstawiono podpowiadanie typów na przykładzie interfejsu IProduct. Dwie różne klasy, FruitStore i CitrusStore, implementowały ten sam interfejs. Dzięki podpowiadaniu typów obie klasy mogły być przekazane do funkcji, która wymagała obiektu typu IProdukt jako parametru. Jeżeli przyjrzysz się temu programowi, zobaczysz, w jaki sposób operacja wykorzystuje interfejs klasy zamiast jej samej. Rysunek 3.3 przedstawia pierwszy krok prowadzący do wykorzystania interfejsów: tworzenie instancji obiektów, tak aby stanowiły instancję interfejsu (pełny kod programu znajduje się w podrozdziale „Podpowiadanie typów: prawie typowanie”).

54



Rozdział 3. Podstawowe zagadnienia wzorców projektowych

Rysunek 3.3. Tworzenie instancji konkretnych implementacji interfejsu IProduct

Na rysunku 3.3 linie, w których tworzone są instancje, pokazują coś odwrotnego do pierwszej zasady: zmienne otrzymują typ konkretnej klasy zamiast interfejsu, który ta klasa implementuje. Tym, czego w PHP nie widać z powodu braku typowania, jest przyjmowanie przez zmienne także typu interfejsu. Kiedy wykorzystane zostanie podpowiadanie typów, programista będzie zmuszony dołączyć obiekt o podanym typie. Jeżeli podpowiedź dotyczy interfejsu, program będzie działał dokładnie tak, jakbyś cały czas wykorzystywał interfejsy. Rysunek 3.4 przedstawia szczegóły tego procesu.

Rysunek 3.4. Metoda wykorzystująca podpowiadanie typów

Przyglądając się rysunkom 3.3 i 3.4, można zauważyć, że główną metodą jest doInterface(). Metoda zawiera podpowiedź typu. Wynik działania metody jest przewidywalny, kiedy jako parametr wykorzystana zostanie jedna z dwóch instancji klas konkretnych implementujących interfejs. Dopóki wszystkie inne implementacje interfejsu zachowają interfejs IProduct (łącznie z typami danych), nie ma znaczenia, jak złożony stanie się program. Wszystkie zmiany i dodatki, które wprowadzisz, będą działały zgodnie z przewidywaniami i nie zepsują pozostałych części programu.

Klasy abstrakcyjne i ich interfejsy Aby rozjaśnić idee wykorzystania w programowaniu interfejsów zamiast implementacji, przykłady przedstawiają interfejsy deklarowane za pomocą słowa kluczowego interface. Zanim jednak zaczniemy, zapamiętaj, że koncept interfejsu odnosi się do metod i ich sygnatur. Każda klasa posiada interfejs składający się z sygnatur jej klas. Ponieważ wzorce projektowe rzadko zawierają dziedziczenie klas konkretnych, musisz zrozumieć, że dziedziczenie po klasie abstrakcyjnej jest tym samym co implementacja interfejsu.

Podstawowe zasady wzorców projektowych



55

W następnym przykładzie klasa abstrakcyjna jest rozszerzana przez dwie proste implementacje. Klasa Client wykorzystuje następnie podpowiadanie typów w celu demonstracji użycia klasy abstrakcyjnej jako interfejsu. Komentarze w kodzie mają za zadanie pomóc w zapewnieniu odpowiedniego typu zwracanego. Klasa abstrakcyjna IAbstract posiada chronioną wartość ($valueNow), dwie chronione metody abstrakcyjne (giveCost i giveCity) oraz publiczną funkcję, która nie jest abstrakcyjna (displayShow):

Dwie implementacje klasy abstrakcyjnej implementują metody abstrakcyjne na różne sposoby:

Patrząc na dwie różne implementacje klasy abstrakcyjnej, można zauważyć, że wykorzystanie interfejsu w tym kontekście oznacza wykorzystanie interfejsu klasy, a nie interfejsu jako struktury definiowanej za pomocą słowa kluczowego interface. Klasa Client zawiera metodę wykorzystującą podpowiadanie typów, która określa klasę abstrakcyjną jako interfejs:

Wynik działania programu będzie następujący: Koszt: $210.54 dla Moose Breath Koszt: $105.27 dla Rattlesnake Gulch

Koszty dla regionów są różne, ponieważ klasy konkretne NorthRegion i WestRegion implementują metody abstrakcyjne w różny sposób. Jeżeli użyty zostanie niewłaściwy typ danych (na przykład string), otrzymasz następujący komunikat: Catchable fatal error: Argument 1 passed to Client::showInterface() must be an instance of IAbstract, string given, called in /Library/

Podpowiadanie typów może więc na swój sposób pomóc Ci w przejściu na korzystanie z interfejsów zamiast implementacji. Aby przekonać się, jak użyteczny jest taki styl programowania, dodaj do programu jeszcze dwie implementacje klasy IAbstract — SouthRegion i EastRegion. Pamiętaj, aby dla metody giveCost() zwracać wartość typu decimal, a dla giveCity() typu string. Utrzymaj resztę interfejsu dla obu nowych klas i wykorzystaj je w klasie Client. Powinieneś zaobserwować, jak łatwo jest dodawać nowe rzeczy do tego typu rozwiązań.

Podstawowe zasady wzorców projektowych



57

Druga zasada wzorców projektowych Niektórzy programiści uważają, że powtórne wykorzystanie obiektów jest tym samym co dziedziczenie. Klasa może być bogatą kolekcją metod i właściwości, a dziedzicząc ją, możemy powtórnie wykorzystać te obiekty bez konieczności ich ponownego tworzenia. Rozszerz klasę, dodaj wymagane nowe metody, właściwości i… to wszystko. Rozszerzanie może jednak prowadzić do problemów w postaci silnie związanych obiektów. Druga zasada wzorców projektowych odnosi się do nadmiernego dziedziczenia: złożoność obiektów jest lepsza od dziedziczenia. Jaka jest więc różnica między złożonością obiektów a dziedziczeniem? Propozycja ta nie wiąże się z pozbyciem się dziedziczenia. Oznacza, że w miejscach, w których jest to możliwe, powinniśmy wykorzystać złożoność zamiast dziedziczenia. Dzięki temu klasy potomne nie zawierają nieużywanych metod i właściwości.

Przykład złożoności przy wykorzystaniu klienta Aby zobaczyć różnicę między dziedziczeniem a złożonością, przyjrzyjmy się prostemu przykładowi klasy nadrzędnej i podrzędnej (dziedziczenie) oraz dwóm odrębnym klasom (złożoność). Zanim zajrzymy w kod, przeanalizujmy rysunek 3.5, przedstawiający różnicę między tymi dwoma podejściami.

Rysunek 3.5. Dziedziczenie i złożoność 58



Rozdział 3. Podstawowe zagadnienia wzorców projektowych

Korzystając z dziedziczenia, klient może stworzyć jedną instancję dla funkcjonalności matematycznej i tekstowej. W przypadku złożoności klient musi stworzyć dwie różne instancje, aby uzyskać obie funkcjonalności. Złożoność w przypadku wzorców projektowych odnosi się zazwyczaj do złożoności jednego z elementów wzorca. Najpierw przeanalizujemy kod wykorzystujący dziedziczenie. Pierwsza klasa (nadrzędna) to prosta klasa wyposażona w metody pozwalające na dodawanie i dzielenie:

Druga klasa pozwala na dodawanie tekstów. Jedna z metod konwertuje numery na ciągi znaków, druga zwraca sformatowany wynik:

W klasie Client tworzona jest instancja klasy InheritMath, dzięki czemu możliwe jest wykorzystanie funkcjonalności matematycznej klasy nadrzędnej oraz wszystkich metod do operacji na ciągach znaków:

W wyniku otrzymamy sformatowaną wartość obliczoną: Twój wynik: 4

Aby uzyskać ten wynik, wykorzystane zostały cztery metody — dwie z nich zostały odziedziczone z klasy nadrzędnej. W przypadku złożoności klasa Client wykorzystuje dwie odrębne klasy — każda z nich zawiera dwie metody. Klasa DoMath jest identyczna z tą z poprzedniego przykładu, zaczniemy więc od przeanalizowania klasy DoText:

Klasa jest podobna do klasy InheritMath. Jest jedna różnica — nie dziedziczy po klasie DoMath. W tym przykładzie klasa Client wykorzystuje obie klasy, a rezultaty są dokładnie takie same. Klient musi jednak stworzyć dwie instancje klas. Poza tą różnicą klasa Client z tego przykładu jest bardzo podobna do klasy z przykładu poprzedniego:

Wynik jest ten sam, ale w klasie Client trzeba wykorzystać więcej klas. Może się to wydawać argumentem na korzyść dziedziczenia, jednak w przypadku większych programów złożoność pozwala zapobiec problemom z wielokrotnym dziedziczeniem klas i błędom przez nie generowanym. Na przykład zmiana w klasie nadrzędnej może przejść do klasy podrzędnej i zepsuć zaimplementowany w niej algorytm.

Delegacja: różnica między MA a JEST W kontekście wzorców projektowych spotkasz się z klasami, które w swej konstrukcji wykorzystują inne klasy. Kiedy jednak klasa przekazuje zadanie do innej klasy, jest to delegacja. To dzięki delegacjom złożoność daje tak ogromne możliwości. W przypadku dziedziczenia każda klasa potomna JEST częścią innej klasy lub klas. W przypadku złożoności klasa może WYKORZYSTYWAĆ inne klasy lub zestaw klas do wykonywania zadań. Nie oznacza to, że dziedziczenie nie powinno być stosowane. Większość wzorców projektowych wykorzystuje dziedziczenie i złożoność jednocześnie. Zamiast stosować dziedziczenie, tworząc serię dzieci, wnuków, prawnuków itd., wzorce projektowe zachęcają do stosowania płytkiego dziedziczenia i funkcjonalności większej liczby klas. Takie podejście pozwala uniknąć ścisłego powiązania między klasami oraz problemów podczas aktualizacji i rozbudowy programu.

Wzorce projektowe jako wielka ściąga Jeśli zastanawiasz się, kiedy i jak skorzystać z delegacji, jak wykorzystać dziedziczenie oraz jak zaprojektować rozwiązanie, z którego będziesz mógł powtórnie korzystać, wzorce projektowe mogą być dla Ciebie ściągą. Możesz szybko rzucić okiem na projekty wykorzystujące diagramy klas, aby zorientować się, gdzie zastosowane zostało dziedziczenie i złożoność. UML (ang. Unified Modeling Language) nauczy Cię, jak czytać diagram klas i szybko rozpoznawać poszczególne części (składniki). Rozdział 4. przedstawia podstawy UML wykorzystywane do zapisu wzorców projektowych.

Wzorce projektowe jako wielka ściąga



61

Organizacja wzorców projektowych Ta książka wykorzystuje podział wzorców projektowych zaproponowany przez Bandę Czterech. Ogólnie mówiąc, wzorce klasyfikowane są pod względem celu i zasięgu. Pod względem celu wyróżniane są trzy typy wzorców:  kreacyjne,  strukturalne,  czynnościowe.

Ta klasyfikacja daje pogląd na wzorce pod względem czynności, jakie wykonują. Pod względem zasięgu wzorce dzielimy na:  klasy,  obiekty.

W tym podrozdziale przyjrzymy się poszczególnym kategoriom. Wyjaśnię, w jaki sposób mogą pomóc w zrozumieniu wzorców.

Wzorce kreacyjne Jak wskazuje nazwa, wzorce kreacyjne wykorzystywane są do tworzenia obiektów. Mówiąc bardziej szczegółowo, pozwalają wprowadzić abstrakcję do tworzenia obiektów. W im większym stopniu w programie stosowana jest złożoność, tym mniej polega on na tworzeniu instancji w kodzie — konieczny jest bardziej elastyczny zestaw zachowań, który może zostać zgrupowany w bardziej zaawansowany zestaw. Wzorce kreacyjne dają możliwość enkapsulowania wiedzy o tym, które klasy konkretne są wykorzystywane przez system, i ukrycia informacji o tworzeniu instancji i złożoności.

Wzorce strukturalne Wzorce skoncentrowane na strukturze rozwiązania to wzorce strukturalne. Wzorce strukturalne wykorzystują dziedziczenie do tworzenia interfejsów lub implementacji. Opisują sposoby łączenia obiektów w celu stworzenia nowej funkcjonalności. Zrozumienie wzorców strukturalnych prowadzi do zrozumienia i wykorzystania skorelowanych klas jako składników wzorców projektowych.

Wzorce czynnościowe Największa liczba wzorców to wzorce czynnościowe. Te wzorce skupiają się na algorytmach oraz rozdzielaniu obowiązków pomiędzy obiektami. Gamma i inni zaznaczają, że te wzorce to więcej niż wzorce obiektów i klas. Opisują komunikację pomiędzy klasami i obiektami.

Kategoria klas Pierwsza z dwóch kategorii zasięgu to klasy. Te wzorce skupiają się na relacjach pomiędzy klasami i ich podklasami. Z 24 wzorców opisanych w tej książce GoF do kategorii klas zalicza tylko cztery. Relacje nawiązywane są poprzez dziedziczenie, a GoF przedkładają złożoność nad dziedziczenie. Wzorce w tej grupie są statyczne, a więc nie zmieniają się po skompilowaniu.

62



Rozdział 3. Podstawowe zagadnienia wzorców projektowych

Kategoria obiektów W kategorii obiektów znajduje się większość wzorców, z których kilka, podobnie jak te z kategorii klas, wykorzystuje dziedziczenie. Wzorce te różnią się od tych z kategorii klas tym, że skupiają się na obiektach, które mogą ulec zmianie podczas działania programu — te wzorce są bardziej dynamiczne.

Wybór wzorca projektowego Częścią nauki wzorców projektowych jest nauka o tym, jak wybrać najbardziej odpowiedni z nich. Wzorce projektowe to nie szablony. Są ogólnymi strategiami pozwalającymi radzić sobie z ogólnymi problemami programowania obiektowego. Książka zawiera po jednym wzorcu z każdej z trzech grup celowych i każdej z dwóch grup zasięgu. Zawiera także trzy rozdziały (12. – 14.) przedstawiające typowe wykorzystanie PHP z bazą MySQL, w którym przydatne mogą być wzorce projektowe. Trzy omawiane tu wzorce są wzorcami czynnościowymi i należą do kategorii obiektów.

Czym spowodowana jest konieczność powtórnego projektowania? Pierwszym z powodów zastanawiania się nad skorzystaniem z wzorców projektowych jest pytanie: „Czym spowodowana jest konieczność powtórnego projektowania?”. Załóżmy, że musisz stworzyć stronę na potrzeby obsługi klienta. Użytkownicy wprowadzają prośby, a odpowiedzi przechowywane są w bazie danych. Możesz jednak zaplanować, że zarówno typy próśb, jak i odpowiedzi mogą się zmieniać. Jeżeli Twój program będzie uzależniony od pewnych operacji, wprowadzanie zmian może powodować problemy. Zamiast umieszczać operacje wykonywane w odpowiedzi na prośby klienta w kodzie, można wykorzystać wzorzec Łańcuch Odpowiedzialności, który pozwala na przekazywanie żądania, aby je wypełnić, dzięki czemu więcej obiektów ma okazję obsłużyć żądanie. Ta książka daje wskazówki, który wzorzec projektowy może pomóc w przypadku konkretnych problemów programistycznych. Rozdziały i wzorce przedstawiane są w kontekście pozwalającym nie tylko zrozumieć wzorzec, ale także problemy przezeń rozwiązywane.

Co się zmienia? Jednym z kryteriów wyboru wzorca projektowego jest rodzaj zmian, jakie mogą wystąpić w projekcie. Zamiast ustalać, czym spowodowana jest konieczność powtórnego projektowania aplikacji, można przyjrzeć się, jakie zmiany chciałbyś wprowadzać bez konieczności powtórnego projektowania. Jak zobaczysz, chodzi o to, aby enkapsulować te części, które mogą podlegać późniejszym zmianom. Tabela 3.1 przedstawia dziewięć wzorców z tej książki, podzielonych zgodnie z przeznaczeniem, zasięgiem i zmianami, na których wprowadzanie pozwalają. Każdy ze wzorców ma ogólne zastosowanie. Wzorzec musi być rozpatrywany w kontekście, który zostanie zaprezentowany wraz ze wzorcem.

Wybór wzorca projektowego



63

Tabela 3.1. Przeznaczenie, zasięg i dozwolone zmiany wzorców projektowych Przeznaczenie

Zasięg

Nazwa

Co może ulec zmianie

Kreacyjny

Klasa

Metoda Fabrykująca

Podklasa obiektu, którego instancja jest tworzona

Obiekt

Prototyp

Klasa obiektu, którego instancja jest tworzona

Strukturalny

Klasa

Adapter *

Interfejs obiektu

Obiekt

Adapter * Dekorator

Odpowiedzialność obiektu bez konieczności tworzenia podklas

Czynnościowy

Klasa

Metoda Szablonowa

Kroki algorytmu

Obiekt

Stan

Stany obiektu

Obiekt

Strategia

Algorytm

Obiekt

Łańcuch Odpowiedzialności

Obiekt, który odpowiada na żądanie

Obiekt

Obserwator

Liczba obiektów zależnych od innej klasy; ile obiektów może być aktualizowanych

* Wzorzec Adapter występuje w dwóch konfiguracjach: klasy i obiektu. Obie konfiguracje zostały opisane w rozdziale 7.

Unikatowe rozwiązywanie problemów: czy wzorce projektowe to odpowiedzi w puszce? Ponieważ wzorce projektowe są rozwiązaniem problemów powtarzających się w programowaniu, niektórzy uważają je za odpowiedzi w puszce i programistyczne gotowce. To tak, jakby powiedzieć, że struktura pętli jest stałą częścią programowania. Pętle, podobnie jak wzorce projektowe, zostały wprowadzone do obsługi pewnego powtarzającego się problemu — w przypadku pętli są to powtórzenia. Jedna pętla powtarzająca czynność sto razy jest na pewno lepsza niż konieczność pisania stu powtarzających się poleceń. Podobnie wykorzystanie wzorca z luźnym łączeniem jest lepsze niż konieczność przepisywania programu przy każdych wprowadzanych zmianach. Tak jak istnieje wiele zastosowań pętli, tak i dla wzorców projektowych znajdziesz na pewno wiele różnych zastosowań.

Czym różnią się wzorce projektowe od frameworków? Wzorce projektowe są elementami mniejszymi architektonicznie i bardziej abstrakcyjnymi niż frameworki. Co więcej, wzorce projektowe są mniej wyspecjalizowane. W rezultacie wzorce projektowe bardziej nadają się do wielokrotnego użytku i są bardziej elastyczne. Zaletą frameworków, podobnie jak zaletą szablonów, jest to, iż są bardziej instruktywne. Jaśniej instruują, w jaki sposób zbudować strukturę lub rozwiązać problem. Niestety, łatwość użycia okupiona jest mniejszą elastycznością architektoniczną. Budowanie aplikacji przy wykorzystaniu frameworka jest znacznie szybsze, ale wynik pracy jest ograniczony przez sam framework. Framework może zawierać struktury obiektowe i często będą one miały budowę warstwową — każda warstwa będzie obsługiwała jakiś aspekt projektu. Niektóre cechy frameworki współdzielą z wzorcami projektowymi, jednak wzorce nie są tak wyspecjalizowane, rozbudowane ani konkretne jak frameworki. 64



Rozdział 3. Podstawowe zagadnienia wzorców projektowych

ROZDZIAŁ 4.

Wykorzystanie UML-a we wzorcach projektowych

Rzeczywistość wydaje się bezwartościowa w porównaniu ze snami rozgorączkowanej wyobraźni; rzeczywistość jest więc porzucana. — Emile Durkheim Najwcześniejszą fazą formacji społecznych, zaobserwowaną w historycznych i współczesnych strukturach społecznych, jest relatywnie mały krąg skupiony przeciwko sąsiednim, obcym lub w jakimś sensie antagonistycznym kręgom. — Georg Simmel Ani życie jednostki, ani też historia społeczeństwa nie mogą być zrozumiałe bez zrozumienia obu. — C. Wright Mills

Po co język modelowania UML? W swej książce Wzorce projektowe. Elementy oprogramowania obiektowego wielokrotnego użytku Gamma, Helm, Johnson i Vlissides wykorzystują UML, który może delikatnie odbiegać od standardów z lat 1990 oraz od współczesnych standardów UML 2.0. Są to jednak drobne odstępstwa, a dzięki wykorzystaniu wersji UML stworzonej przez GoF będziesz w stanie porównać wzorce PHP z tej książki z wzorcami oryginalnymi. Niezależnie więc od zalet innych wersji UML, przedstawianych jako najbardziej odpowiednie dla programistów, w tej książce zachowana zostanie spójność z wyżej wspomnianą książką. Jeżeli nie znasz UML-a, będziesz potrzebował doświadczenia. Podczas pracy z wzorcami ich znaczenie w kontekście projektu i konkretnej implementacji w PHP będzie stawało się coraz jaśniejsze. Implementacja wzorca bezpośrednio z diagramu UML jest, ze względu na minimalne możliwości typowania w PHP, bardzo trudna — musisz dobrze zrozumieć wzorzec i odpowiednio zmodyfikować wykorzystywane typowanie.

65

Diagramy klas Proste diagramy klas wykorzystane zostały w poprzednich rozdziałach — w tym podrozdziale przedstawię je dokładniej oraz omówię sposób ich wykorzystania we wzorcach projektowych. Diagramy klas we wzorcach przedstawiają relację i komunikację pomiędzy uczestnikami (klasami i interfejsami). Rysunek 4.1 przedstawia typowy diagram klas wzorca projektowego. Na diagramie znajduje się pięciu uczestników:  Client (w domyśle)  Creator  ConcreteCreator  Product  ConcreteProduct

Każdy z uczestników to klasa lub interfejs (klasa abstrakcyjna lub interfejs). Nazwy interfejsów zapisane zostały kursywą, natomiast nazwy klas konkretnych zostały pogrubione. Zauważ, że klasa Client została przedstawiona jako wyszarzona ramka. Oznacza to, że klasa ta jest domyślna i nie jest częścią wzorca Metoda Fabrykująca.

Rysunek 4.1. Diagram klas (Metoda Fabrykująca)

Klasa Client jest często integralną częścią wzorca; czasami jest w domyśle, a czasami w ogóle nie występuje na diagramie. Klienci wykonują żądania z głównego programu, a strzałki pomagają zobrazować relacje pomiędzy klasą Client a głównym programem. Programiści często wykorzystują określenie „główna” w stosunku do klienta, jednak w przypadku wzorców projektowych jest to mylące — główna część programu to skorelowani uczestnicy, a nie klasa Client. Rysunek 4.2 przedstawia ten sam wzorzec bez adnotacji z pseudokodem — dzięki temu uwidocznione są podstawowe elementy wzorca.

66



Rozdział 4. Wykorzystanie UML-a we wzorcach projektowych

Rysunek 4.2. Diagram klas (Metoda Fabrykująca bez adnotacji)

Wzorzec Metoda Fabrykująca pozwala na tworzenie instancji klas za pośrednictwem fabryki (Creator) — dzięki temu proces tworzenia instancji odbywa się poza metodą wykonującą żądanie. Klient potrzebuje (żąda) produktu, ale zamiast bezpośrednio tworzyć instancję klasy ConcreteProduct, musi wykonać żądanie (zgodnie z diagramem) za pośrednictwem klasy Creator. Bez diagramu klas nie byłoby jednoznacznie wiadomo, w jaki sposób klient powinien wykonać żądanie, także pozostałe relacje między uczestnikami byłyby niemożliwe do określenia. Dzięki diagramowi klas możesz szybko sprawdzić relacje występujące we wzorcu projektowym. Dokładna charakterystyka relacji także jest przedstawiona na diagramie, kolejne podrozdziały wyjaśniają symbole wykorzystywane do przedstawiania uczestników i relacji.

Symbole uczestnictwa GoF określa klasy i interfejsy składające się na wzorzec projektowy mianem uczestników. Nazwa uczestnika jest pogrubiona. Interfejsy (klasy abstrakcyjne i interfejsy) pisane są kursywą. Pod nazwą niektórych uczestników narysowana jest linia, a pod nią lista najważniejszych metod. Metody abstrakcyjne pisane są kursywą, a metody konkretne normalną czcionką. Rysunek 4.3 przedstawia tylko interfejs Creator.

Rysunek 4.3. Elementy uczestnika będącego interfejsem Symbole uczestnictwa



67

W konkretnych implementacjach interfejsów metody abstrakcyjne stają się konkretne. Rysunek 4.4 przedstawia implementację interfejsu Creator.

Rysunek 4.4. Klasa i metoda konkretna implementująca klasę abstrakcyjną lub interfejs

Zauważ, że metoda AnOperation() nie została zawarta w klasie konkretnej. Metoda ta została już zaimplementowana i jest dziedziczona przez klasę ConcreteCreator. Metoda FactoryMethod() musi natomiast zostać zaimplementowana, ponieważ w klasie nadrzędnej została zadeklarowana jako abstrakcyjna. Ostatnim elementem związanym z uczestnikami są adnotacje z pseudokodem. Rysunek 4.5 przedstawia adnotacje usunięte ze wzorca Metody Fabrykującej.

Rysunek 4.5. Adnotacje z pseudokodem

Który UML jest najlepszy? Jak w przypadku większości rzeczy związanych z programowaniem obiektowym i wzorcami projektowymi najlepsze zasoby pochodzą z oryginalnego źródła. Jak już wcześniej wspomniałem, UML wykorzystywany w tej książce to ten sam, z którego korzystała Banda Czterech. Korzenie UML-a można jednak znaleźć w pracach Boocha, Jacobsona i Rumbaugha. To oni stworzyli w połowie lat 90. UML i zaproponowali go grupie OMG (Object Management Group), zajmującej się standardami w przemyśle informatycznym. UML został zaakceptowany jako standard w roku 1997 i od tamtej pory, wraz ze zmianami, jakie zachodziły w modelach, przeszedł wiele modyfikacji. Doskonałe źródło wiedzy stanowi rozdział 5. książki Object-Oriented Analysis and Design with Applications, 3rd Edition (Addison-Wesley). Ten stustronicowy rozdział dokładnie omawia jedną z najbardziej aktualnych wersji UML-a z organizacji OMG. Książka Unified Modeling Language User Guide, 2nd Edition Boocha, Jacobsona i Rumbaugha zawiera ponad 500 stron opisu UML-a. Jeżeli więc chcesz dokładniej zapoznać się z UML-em, możesz skorzystać z tych publikacji.

68



Rozdział 4. Wykorzystanie UML-a we wzorcach projektowych

Adnotacje przedstawiają dokładniejsze informacje na temat struktury wzorca projektowego. Mają za zadanie pokazać programiście wymagania odnośnie do kodu dla poszczególnych uczestników.

Notacja relacji Adnotacje z pseudokodem zawierają informacje na temat struktury wzorca projektowego. Zaprojektowane zostały tak, aby dać programiście lepszy pogląd na wymagania odnośnie do kodu uczestników — zobacz rysunek 4.6.

Rysunek 4.6. Relacje między klasami

Relacje i ich znaczenie mogą być różne w zależności od wersji UML-a, a powyższy rysunek przedstawia uproszczony UML relacji opisanych w rozdziale pierwszym książki Design Patterns: Elements of Reusable Object-Oriented Software, w której można znaleźć dokładniejszy ich opis. Gdybyś chciał wykorzystać w PHP dodatkowe, nie opisane w tej książce wzorce, będziesz w stanie zrozumieć wersję UML stosowaną przez GoF. to ja i mój dżej dżej

Relacje asocjacji Najprostszą i prawdopodobnie najczęściej występującą relacją jest asocjacja. Jest to relacja, w której jeden z uczestników przechowuje referencję do innego uczestnika. Na rysunku 4.7, przedstawiającym wzorzec Pełnomocnik, reprezentowana jest przez prostą strzałkę pomiędzy klasami Client i ISubject oraz Proxy i RealSubject.

Notacja relacji



69

Rysunek 4.7. Relacje asocjacji pomiędzy klasami Client i ISubject oraz Proxy i RealSubject

Poniżej znajduje się asocjacja w PHP na przykładzie klasy Proxy:

W kodzie podpowiadana jest referencja do interfejsu IStrategy (uczestnik wzorca o nazwie Strategy w kodzie jest interfejsem o nazwie IStrategy). Klasa Context zawiera referencję do interfejsu Strategy, wykorzystuje mechanizm podpowiadania typów bez potrzeby tworzenia instancji implementacji IStrategy wewnątrz uczestnika Context. Na przykład klient wykonujący żądanie wykorzystałby klasę ConcreteStrategyA:

Notacja relacji



71



Jak widać, interfejs IStrategy zawiera tylko jedną metodę — algorithm(). Klasa Context wygląda, jakby implementowała metodę algorithm(). Klasa Context jest jednak skonfigurowana za pomocą zmiennej przekazywanej w konstruktorze do wykorzystywania konkretnych implementacji interfejsu. Ta cecha relacji obrazuje, w jaki sposób dwa obiekty tworzą asocjację i w jaki sposób ich czas życia jest związany. Ze wszystkich relacji to właśnie asocjacja jest najtrudniejsza do objaśnienia i zrozumienia, ponieważ może przyjmować różne formy. Zamiast próbować tłumaczyć dalej i ryzykować nadmiernym uproszczeniem problemu, poszczególne wykorzystania asocjacji w tej książce będą wyjaśniane indywidualnie.

Relacje implementacji i dziedziczenia Banda Czterech wykorzystuje do określania implementacji i dziedziczenia tę samą notację: trójkąt. Rysunek 4.9 przedstawia klasę abstrakcyjną i interfejs wraz z klasami podrzędnymi.

Rysunek 4.9. Implementacja i dziedziczenie wykorzystują tę samą notację — trójkąt

Pojedyncze dziedziczenie lub implementacja oznaczane są pojedynczym trójkątem (trójkąt na kresce), podczas gdy wielokrotne dziedziczenie oznaczane jest trójkątem połączonym na wspólnej części linii biegnących do klas konkretnych. Być może zastanawiasz się, dlaczego GoF wykorzystuje do oznaczania implementacji i dziedziczenia ten sam symbol. Częściowo powodem jest to, że we wzorcach projektowych praktycznie każde dziedziczenie to dziedziczenie po klasie abstrakcyjnej. Klasy abstrakcyjne implikują implementację metod abstrakcyjnych, niezależnie więc, czy wykorzystane zostaną klasy abstrakcyjne, czy interfejsy, implementacja jest częścią relacji. W wielu miejscach swej książki GoF wykorzystuje klasy abstrakcyjne i interfejsy wymiennie (określając je jako interfejsy),

72



Rozdział 4. Wykorzystanie UML-a we wzorcach projektowych

ponieważ oba typy zawierają interfejs ich klas pochodnych. W wielu wzorcach nie ma znaczenia, czy wykorzystany zostanie interfejs, czy klasa abstrakcyjna. W jednej implementacji wzorca programista może wykorzystać klasy abstrakcyjne, podczas gdy w innej mogą zostać wykorzystane interfejsy.

Polimorfizm i warunki użycia Dla niektórych czytelników książek autorstwa GoF problemem jest to, że pewne terminy wykorzystywane są zamiennie. Po pierwszych próbach zrozumienia zagadnień w książce możesz chcieć napisać do autorów, aby ujednolicili nazewnictwo, ale okazuje się, że podobnie jak w programowaniu, także w pisaniu funkcjonuje polimorfizm. Poniżej znajduje się kilka przykładów wraz z ich interpretacją: Interfejsy GoF określa mianem interfejsów interfejsy i klasy abstrakcyjne. W ten sposób odnosi się (i ja także) do zestawu niezaimplementowanych metod i właściwości, które wchodzą w skład klasy abstrakcyjnej lub interfejsu. Ważną cechą obydwu jest to, że pozwalają na rozluźnienie więzów we wzorcu. Implementacja Podobnie jak jest w przypadku określenia interfejs, termin implementacja może być wykorzystany w różnych kontekstach. Zarówno klasy abstrakcyjne, jak i interfejsy muszą być w jakimś sensie implementowane. Każda metoda abstrakcyjna w klasie abstrakcyjnej musi zostać zaimplementowana w klasach potomnych. Mimo że klasa potomna dziedziczy interfejs klasy abstrakcyjnej, musi zaimplementować metody abstrakcyjne. Implementacja klasy abstrakcyjnej oznacza więc zarówno dziedziczenie, jak i implementację interfejsu. Łatwiej powiedzieć „implementuje” niż „implementuje i dziedziczy”. Takie określenie pozwala też zaznaczyć, że klasa potomna musi zawierać konkretną implementację. Operacja W całej książce, łączenie z diagramami klas, spotykał będziesz termin operacje. Generalnie odnosił się on będzie do części programu, która coś robi. Operacja może być synonimem funkcji lub metody albo może się odnosić do kilku rzeczy zachodzących między klasami. Na przykład może oznaczać utworzenie instancji klasy konkretnej. Możesz się spotkać z określeniem operacja jako stworzenie klasy ClassB otaczającej klasę ClassC i tworzącej jej instancję. Operacja w tym kontekście może oznaczać część funkcji lub po prostu polecenie w konstruktorze. Elastyczne myślenie Czytając tę książkę oraz książkę autorstwa GoF, będziesz musiał myśleć o programowaniu w inny sposób. Myśl bardziej ogólnie niż tylko o algorytmach, w szczególności musisz myśleć o relacjach między obiektami. Może to wiązać się częściowo z myśleniem algorytmicznym, ale przede wszystkim umieszczaj rzeczy w kontekście ich relacji z innymi obiektami i myśl o ich miejscu w całym programie — dzięki temu wszystko będzie łatwiejsze. Dobre algorytmy będą zawsze ważne, ale ich miejsce jest w metodach i obiektach. Ważne są procesy zachodzące między obiektami i powtórne wykorzystanie klas. Jeżeli komponenty obiektowe zostaną wykorzystane poprawnie, zawsze będą w stanie obsłużyć nowy algorytm. O ile powiązania między obiektami wykonane są poprawnie, wprowadzenie nowego algorytmu nie powinno mieć wpływu na pozostałe elementy programu. Dlatego właśnie powinieneś myśleć relacjami, a nie sekwencjami poleceń, które pozwolą rozwiązać bieżący problem.

Notacja relacji



73

Relacje tworzenia Kiedy we wzorcu projektowym jeden obiekt tworzy instancję innego, obrazowane jest to przerywaną strzałką skierowaną w stronę obiektu tworzonego. Taką relację znajdziesz we wzorcach tworzących takich jak Metoda Fabrykująca (rozdział 5.) lub Fabryka Abstrakcyjna, ale także w innych wzorcach. Rysunek 4.10 przedstawia diagram klas wzorca Metoda Fabrykująca, w którym klasa ConcreteCreator wykorzystuje metodę factoryMethod(), aby stworzyć instancję klasy ConProductA i ConProductB. Client przechowuje referencje do interfejsów Creator i Product, aby móc określić, jaki dokładnie produkt jest potrzebny.

Rysunek 4.10. Relacje tworzenia pomiędzy klasą ConcreteCreator a produktami

Podczas rozpatrywania relacji asocjacji pomiędzy klasami w PHP w niektórych przypadkach może zajść konieczność stworzenia instancji obiektu, aby przechować relację innej klasy lub interfejsu. Takie tworzenie instancji ma za zadanie stworzenie typu w celu przechowywania referencji, a nie wykorzystania tworzonego obiektu. W takim przypadku w PHP nie należy używać przerywanej strzałki. Taka relacja to asocjacja — mimo że tworzona jest instancja, jej tworzenie ma tylko zainicjować asocjację.

Relacje wielokrotne Czasami spotkasz się z diagramem klas, w którym strzałki relacji asocjacji lub agregacji będą zakończone kółkiem, tak jak na rysunku 4.11. Czarne kółko oznacza referencję lub agregację wielu obiektów. Wzorzec Pyłek wykorzystywany jest do przechowywania referencji do wielu instancji współdzielonych obiektów lub różnych obiektów.

74



Rozdział 4. Wykorzystanie UML-a we wzorcach projektowych

Rysunek 4.11. Czarne kółko oznacza referencję do wielu obiektów

Diagramy obiektów W przeciwieństwie do diagramu klas, który przedstawia klasy, diagram obiektów zawiera wyłącznie instancje i strzałki pokazujące referencje. Konwencja nazewnicza przewiduje poprzedzanie nazw instancji małą literą a. Instancja klasy o nazwie Client przyjęłaby nazwę aClient. Tekst znajdujący się w dolnej połowie modułów diagramu obiektów oznacza nazwę docelowego obiektu lub metody. Dobrym przykładem wykorzystania diagramu obiektów może być wzorzec Łańcuch Odpowiedzialności. Wzorzec pomaga w sytuacji, gdy więcej niż jeden obiekt ma obsłużyć żądanie — np. w aplikacji helpdesk. Klient wykonuje żądanie, a następnie interfejs obsługujący przekazuje żądanie, dopóki nie zostanie odnaleziona klasa konkretna mająca obsłużyć zdarzenie. Rysunek 4.12 ilustruje taki łańcuch.

Rysunek 4.12. Wzorzec Łańcuch Odpowiedzialności przesyła żądanie wzdłuż łańcucha obiektów obsługujących

Na rysunku 4.12 żądanie przekazywane jest z A do B, następnie do C i tak dalej, dopóki nie zostanie wykonane, a wyszukiwanie nie zostanie zakończone. Rysunek 4.13 pokazuje ten sam proces na diagramie obiektów.

Diagramy obiektów



75

Rysunek 4.13. Diagram obiektów wzorca Łańcuch Odpowiedzialności

Diagramy obiektów dają możliwość przyjrzenia się implementacji wzorca projektowego i relacjom pomiędzy obiektami we wzorcu z innej perspektywy. Pomagają objaśnić powiązania między obiektami.

Diagramy interakcji Ostatnim diagramem wykorzystywanym przez GoF jest diagram pozwalający na śledzenie kolejności obsługi żądania. Diagramy interakcji są podobne do diagramów obiektów pod względem nazewnictwa obiektów, jednak ich przepływ jest pionowy — z góry na dół. Strzałki z linią ciągłą wskazują kierunek przesyłania żądania, a strzałki z przerywaną linią oznaczają tworzenie instancji. Rysunek 4.14 przedstawia ten sam Łańcuch Odpowiedzialności, który pokazany został na rysunku 4.13 (szara strzałka kierunku i opisy czasu zostały dodane, ale normalnie nie występują w diagramach interakcji).

Rysunek 4.14. Diagram interakcji wzorca Łańcuch Odpowiedzialności

Pionowe prostokąty obrazują czas, w którym obiekt jest aktywny. Jak widać na rysunku 4.14, obiekt Client jest aktywny przez cały czas, a pierwsza konkretna klasa obsługująca staje się aktywna po wysłaniu żądania przez klienta. Druga konkretna klasa obsługująca nie jest aktywna, dopóki żądanie nie opuści pierwszej klasy.

76



Rozdział 4. Wykorzystanie UML-a we wzorcach projektowych

Rola diagramów i notacji w programowaniu obiektowym Zanim zajmiemy się pierwszym wzorcem projektowym i jego implementacją w PHP, chciałbym zatrzymać się na chwilę i przyjrzeć, do czego zmierzam, opisując diagramy i notacje. Dla przypomnienia: diagramy i powiązane z nimi notacje pozwalają na tłumaczenie, przeglądanie i budowanie wzorców projektowych. Powinniśmy z nich korzystać, dopóki są w stanie nam pomóc. Jednak w chwili, kiedy przestają być użyteczne, powinny zostać porzucone, a na ich miejsce powinny powstać nowe. Przez lata jedynym programistycznym diagramem, jaki znałem, był diagram przepływu. Diagramy takie wyglądają dobrze i rozumiem ich użyteczność, chociaż sam rzadko z nich korzystam podczas planowania programowania. Patrząc na nie teraz, uświadamiam sobie, że w przypadku programowania obiektowego mogą być bardzo złym narzędziem. Diagramy przepływu są użyteczne przy programowaniu sekwencyjnym. UML przeznaczony na potrzeby programowania obiektowego jest zupełnie inny — pozwala podzielić program na moduły nazywane klasami, a notacje pozwalają zobrazować zależności i interakcje.

Narzędzia dla UML Jeżeli poszukasz, najprawdopodobniej znajdziesz narzędzia pomagające rysować diagramy UML, ja jednak je odradzam. W książce Unified Modeling Language User Guide, 2nd Edition Grady Booch pisze o zautomatyzowanych narzędziach UML: Jedną z cech automatyzacji tworzenia diagramów w dowolnej notacji jest to, że takie narzędzia pozwalają złym projektantom tworzyć okropne projekty o wiele szybciej, niż byliby to w stanie robić bez nich.

Jeżeli potraktujesz UML jako pomoc w procesie obmyślania programu, Twoje projekty będą lepsze i będziesz w stanie lepiej zrozumieć wzorce projektowe. Na przykład rysunek 4.15 przedstawia diagram (wzorzec projektowy Strategia), który z powodzeniem wykorzystałem podczas projektu w PHP.

Rysunek 4.15. Projekt rozrysowany na papierze

Narzędzia dla UML



77

Kiedy zaczynam pracować nad projektem, powstają kolejne szkice, zawierające więcej szczegółów, przypisów, komentarzy, a nawet elementy kodu. Podczas szkicowania wzorca i jego szczegółów zmuszony jestem do myślenia. Przy większych projektach i grupach programistów narzędzia do rysowania diagramów mogą być użyteczne do komunikacji między programistami. Obecnie jednak moje główne narzędzia podczas pracy z diagramami UML to kartka i ołówek (z upodobaniem wykorzystuję stare kartki z kalendarza).

Inne diagramy UML W przypadku diagramów UML nie należy myśleć w kategoriach standardów — należy zastanowić się, w jaki sposób dany diagram może być wykorzystany. Booch sugeruje podział diagramów na strukturalne i czynnościowe, ze wzorcami interakcji jako dużą podklasą wzorców czynnościowych. Dla mnie wyjątkowo użyteczne podczas pracy z maszynami stanowymi — w szczególności podczas pracy ze wzorcem Stan — okazały się diagramy stanów. Diagramy stanów pozwalają programiście skupić się na stanach, zmianach stanów, wyzwalaczach i przekształceniach. Mimo tego, że diagramy stanów nie są częścią UML-a wykorzystywanego przez GoF, mogą być bardzo użyteczne podczas pracy z maszynami stanowymi (przykłady diagramów stanów zostały przedstawione w rozdziale 10.). Aby zrozumieć wzorce projektowe, musisz nie tylko poznać notacje, ale także zrozumieć, co oznaczają. Na przykład o wiele ważniejsze jest, aby rozumieć, co oznacza agregacja, niż wiedza, że oznaczana jest linią zakończoną z jednej strony diamentem, a z drugiej strzałką. Jeżeli zaczniesz korzystać z diagramów wzorców projektowych, będziesz lepiej rozumiał problemy programistyczne, w których rozwiązaniu pomagają poszczególne wzorce. Gdy skorzystasz z kartki i ołówka, Twój proces myślowy będzie płynniejszy niż podczas korzystania z narzędzi automatyzujących, a szansa na zrozumienie wzorców projektowych zwiększy się.

78



Rozdział 4. Wykorzystanie UML-a we wzorcach projektowych

CZĘŚĆ II

Wzorce kreacyjne

Wyobraźnia jest początkiem tworzenia. Wyobrażasz sobie to, czego pragniesz; chcesz tego, co sobie wyobraziłeś; i w końcu tworzysz to, czego chcesz. — George Bernard Shaw Bez kultury i relatywnej wolności, którą ona niesie, społeczeństwo, nawet doskonałe, jest tylko dżunglą. Dlatego właśnie każda kreacja jest podarunkiem dla przyszłości. — Albert Camus Gdybym był obecny przy stworzeniu, dałbym trochę pożytecznych wskazówek dla lepszego urządzenia wszechświata. — Alfons X Mądry Wzorce kreacyjne to wzorce, które skupiają się na procesie tworzenia instancji. Są zaprojektowane tak, aby oddzielić proces tworzenia od stworzonych instancji i enkapsulować wiedzę wykorzystywaną przez obiekty. Gamma, Helm, Johnson i Vlissides wyróżniają pięć wzorców kreacyjnych:  Fabryka Abstrakcyjna  Budowniczy  Metoda Fabrykująca  Prototyp  Singleton

Z tych pięciu wzorców do demonstracji implementacji w PHP wybrane zostały Metoda Fabrykująca i Prototyp. Ze wszystkich tych wzorców tylko Metoda Fabrykująca zaliczana jest do zasięgu klas — jest to relatywnie prosty, ale bardzo informatywny wzorzec. Wzorzec Prototyp zaliczany jest do zasięgu obiektów i może zostać zaimplementowany przy użyciu metody PHP __clone(). Instancje tworzone są na podstawie prototypu, a kolejne obiekty są klonowane. Myślę, że uznasz ten wzorzec za łatwy w użyciu i użyteczny. Najciekawsze we wzorcach kreacyjnych jest to, że ich użycie jest ważne (a w zasadzie niezbędne) w programach, które rozrastając się, zaczynają polegać bardziej na zestawie obiektów niż podległości klas. Kiedy programy stają się systemami opartymi na obiektach składających się z innych obiektów, tworzenie każdego z obiektów nie powinno być zależne od twórcy. Innymi słowy, obiekty nie powinny być silnie związane z procesami, które je stworzyły. Dzięki temu zestaw obiektów nie jest osadzony w żadnej części obiektu wysyłającego żądanie. Wzorce projektowe przedstawione w części II przedstawiają optymalny sposób tworzenia obiektów. Rysunek II.1 przedstawia sposób, w jaki działają wzorce kreacyjne. 79

Rysunek II.1. Struktura wzorców kreacyjnych

80



Rozdział 5. Wzorzec Metoda Fabrykująca

ROZDZIAŁ 5.

Wzorzec Metoda Fabrykująca

Ruchy kobiet wywołały wśród pracowników fabryk wielką mobilizację, która zniszczyła dawne wzorce. — Emma Bonino Projektowanie to metoda łączenia formy i zawartości. Projektowanie, jako dziedzina sztuki, ma wiele definicji; nie ma jednej definicji. Projektowanie może być sztuką. Projektowanie może być estetyczne. Projektowanie jest bardzo proste, dlatego właśnie jest takie skomplikowane. — Paul Rand Stwórz swoją własną metodę. Nie polegaj ślepo na mojej. Wymyśl coś, co sprawdzi się dla Ciebie! Ale łam konwencje, błagam. — Konstanty Stanisławski

Czym jest wzorzec Metoda Fabrykująca? Jako część kategorii wzorców kreacyjnych wzorzec Metoda Fabrykująca ma za zadanie coś tworzyć. W przypadku tego wzorca to coś to produkt niezwiązany z klasą, która go stworzyła. Aby zachowane zostało luźne łączenie, klient przesyła żądanie przez fabrykę. Następnie fabryka tworzy żądany produkt. Można też powiedzieć, że Metoda Fabrykująca uwalnia produkt od klienta wykonującego żądanie. Rysunek 5.1 przedstawia diagram klas dla wzorca. Klasa Client jest domyślna. Jak widać na rysunku 5.1, Client przechowuje referencję do klasy Creator (interfejs fabryki), dzięki której może zażądać produktu graficznego lub tekstowego. Nie tworzy instancji żądanego produktu. Instancje tworzone są w fabrykach konkretnych. Wyobraź sobie, że na przyjęcie z okazji Halloween chcesz zamówić czekoladowe babeczki z czarnym i pomarańczowym lukrem. Dzwonisz do piekarza (creator), który robi dla Ciebie babeczki (product). Nie angażujesz się w proces tworzenia obiektu, który zamówiłeś, a otrzymujesz gotowy produkt.

Czym jest wzorzec Metoda Fabrykująca?



81

Rysunek 5.1. Diagram klas zaimplementowanego wzorca Metoda Fabrykująca

Kiedy korzystać z Metody Fabrykującej Wzorce projektowe częściowo wybiera się na podstawie zmian, jakie chcesz wprowadzać w przyszłości w programie. Tabela 3.1 z rozdziału 3. pokazuje, że ten wzorzec powinien być wykorzystywany, kiedy podklasa tworzonego obiektu może się zmieniać. W przykładach z tego rozdziału wszystkie podklasy interfejsu Project są zmienne — oznaczają różne kraje. Jak się przekonasz, projekty to obiekty składające się z tekstu (zapiski) lub grafiki (mapy). Na początku zakładamy, że programista nie wie, ile krajów może pojawić się w programie. Innymi słowy, liczba i typy obiektów są nieznane. Klasa nie może przewidzieć liczby obiektów, które będzie musiała stworzyć, a więc nie chcemy, aby była z tymi obiektami silnie związana. Jeżeli klasa ma skończoną i znaną liczbę obiektów, które muszą zostać utworzone, można napisać ją tak, aby obiekty były tworzone w przewidywalny sposób. Na przykład: jeżeli robisz aplikację mapy świata z odrębnymi obiektami dla każdego z siedmiu kontynentów, możesz być pewien, że ta liczba nie ulegnie zmianie. Z drugiej strony, jeżeli tworzysz stronę zawierającą różne gatunki insektów, możesz być pewien, że gatunki będą odkrywane, zmieniane, a część z nich wyginie w niedługich odstępach czasu. Produkt programistyczny, który poradzi sobie z taką zmiennością, musi być bardzo elastyczny. Właśnie w takim projekcie powinieneś rozważyć wykorzystanie wzorca projektowego Metoda Fabrykująca.

82



Rozdział 5. Wzorzec Metoda Fabrykująca

Minimalistyczny przykład Pierwszy przykład, rozpoczynający naszą pracę z wzorcem Metoda Fabrykująca, zwraca tylko tekst. Wzorzec tworzony jest na potrzeby programu, w którym programista wie, że będzie musiał tworzyć obiekty tekstowe i graficzne dla projektu zawierającego mapy i notatki. Nie wie natomiast, ile dokładnie par map i notatek będzie potrzebnych, nie wie też, co klient będzie chciał dodać do programu. Powiedziano mu, że konieczne będą mapy w formie graficznej i opisujący je tekst. Na początku tworzy skromny wzorzec Metoda Fabrykująca, wyświetlający na ekranie informacje tekstowe — część w formie „grafiki”, część w formie „tekstu”. Jeżeli rozwiązanie zostanie zaprojektowane prawidłowo, nie będzie problemu z przechowywaniem dowolnej liczby obiektów.

Praca z fabryką Pierwszy krok to stworzenie fabryki: interfejs Creator. W tej implementacji jako interfejs wykorzystamy klasę abstrakcyjną. Jeżeli przyjrzymy się bliżej diagramowi klas, zauważymy, że jedna z adnotacji zawiera metodę konkretną — startFactory(). Dzięki temu wiemy, że interfejs musi być klasą abstrakcyjną, a nie interfejsem — interfejsy mogą zawierać tylko metody abstrakcyjne. Projekt wymaga też metody abstrakcyjnej factoryMethod(). W klasie abstrakcyjnej wszystkie takie metody muszą być abstrakcyjne; w innym przypadku będą traktowane jako konkretne. Plik Creator.php zawiera kod pierwszego uczestnika wzorca.

Zwróć uwagę, że adnotacje na diagramie wskazują, że metoda startFactory() musi zwracać produkt. W implementacji metoda startFactory() oczekuje, że factoryMethod() zwróci obiekt produktu. W związku z tym konkretna implementacja metody factoryMethod() musi stworzyć instancję obiektu implementowanego z interfejsu Product. Dwie konkretne klasy fabryk dziedziczą po klasie Creator i implementują metodę factoryMethod(). Implementacja metody factoryMethod() zwraca produkt graficzny lub tekstowy za pośrednictwem metody klasy Product — getProperties(). Implementacje TextFactory i GraphicFactory zawierają następujące elementy:

Obie implementacje są bardzo podobne — różnią się tylko tym, że jedna tworzy obiekt typu TextProduct, a druga — typu GraphicProduct.

Produkt Drugi interfejs we wzorcu Metoda Fabrykująca to Product. W naszej minimalistycznej implementacji wszystkie klasy implementujące interfejs muszą zaimplementować tylko jedną metodę, getProperties().

Dzięki temu, że klasa nie ma właściwości, możemy w klasach potomnych zdecydować, co dokładnie chcemy zrobić z metodą getProperties(). W PHP, w którym sygnatura zawiera tylko nazwę i widoczność, możemy dowolnie modyfikować metodę abstrakcyjną, włączając w to zwracanie wartości — a jeżeli tylko sygnatura się zgadza, implementacja będzie poprawna. W implementacji Metody Fabrykującej można zaobserwować polimorfizm na przykładzie metody getProperties(). Zostanie wykorzystana do zwrócenia tekstu lub grafiki, ale wiemy, że dopóki będzie posiadała poprawną sygnaturę, będzie dostarczała to, czego potrzebujemy. Dokładnie ta sama metoda, getProperties(), posiada wiele (poli) różnych form (morf). W tym przypadku jedna z form zwraca tekst, a druga grafikę:

84



Rozdział 5. Wzorzec Metoda Fabrykująca

Możesz pomyśleć: „Wielka mi rzecz, to zwraca tylko zmienną tekstową”. Na razie jest to prawdą. Możesz jednak w implementacji zawrzeć, co tylko chcesz, a Metoda Fabrykująca stworzy to i zwróci do metody Client. Kiedy więc zobaczysz wynik „To jest tekst” lub „To jest grafika”, wyobraź sobie dowolny obiekt, który mógłbyś chcieć stworzyć. Kolejna implementacja zwraca abstrakcyjną grafikę w formie tekstu:

Każda z dwóch implementacji fabryki i produktu implementuje metody abstrakcyjne w celu stworzenia dwóch różnych fabryk i produktów, stosując się do interfejsów.

Klient Ostatnim uczestnikiem wzorca jest domyślny klient. Nie chcemy, aby klasa Client wysyłała żądania bezpośrednio do produktów. Chcemy natomiast, aby żądania były przesyłane przez interfejs Creator. Później, jeżeli będziemy dodawać produkty lub fabryki, klient będzie mógł wykonać takie samo żądanie i uzyskać o wiele szerszą gamę produktów bez konieczności wprowadzania zmian w programie:

Jeżeli wszystko zadziała tak jak powinno, otrzymasz wynik: To jest grafika.