kurshtml
Administrator

Płeć: 
Wiek: 30
Dołączył(a): 02 Mar 2004
Posty: 4462
Pomocy: 43
|
Wysłany: 15.02.2010 16:36 [technologie] Rozwój aplikacji |
 |
Większość programistów PHP, w miarę zdobywania kolejnych doświadczeń, rozwija sposób tworzenia aplikacji. Zwykle zaczyna się od prostego programu "Hello World". Potem jakieś drobne wstawki na stronie. Kolejnym naturalnym krokiem w aplikacjach web jest wykorzystanie instrukcji include do tworzenia szkieletu strony internetowej w taki sposób, aby nie trzeba było powielać menu w każdym pliku HTML.
Programowanie proceduralne
Serwis jednak się rozrasta i przychodzi moment, kiedy samo operowanie na dołączanych plikach już nie wystarcza. Wtedy najczęściej w aplikacji pojawiają się funkcje i zmienne globalne - tzn. programowanie proceduralne. Część programistów, dla których WWW to raczej hobby i dobra zabawa niż zajęcie zarobkowe, na tym poprzestaje. Inni jednak przy pierwszej okazji implementacji naprawdę zaawansowanego systemu przekonują się - niejednokrotnie bardzo boleśnie - że nie jest to najlepszy sposób na implementację zaawansowanych aplikacji. Pojawia się całe multum w żaden sposób niepowiązanych ze sobą zmiennych globalnych. Na dodatek dołączane biblioteki innych programistów wchodzą w konflikty nazw, przez co nadpisują nasze zmienne, co często jest bardzo trudne do namierzenia. Funkcje stają się coraz bardziej rozbudowane i przyjmują coraz więcej parametrów, w których trudno się połapać. Na dodatek one również nie są ze sobą w żaden sposób powiązane - mamy po prostu jeden worek, do którego luźno wrzucamy zmienne i funkcje. Przez jakiś czas można sobie z tym próbować radzić, grupując kod w osobne pliki i dodając prefiksy nazw zmienny oraz funkcji, ale to jest tylko obejście. Kod napisany w taki sposób jest niezmiernie trudny do modyfikacji nawet dla osoby, która go sama napisała.
Programowanie zorientowane obiektowo
I wtedy nadchodzi moment, kiedy jedynym ratunkiem jest przejście na obiekty. Choć programowanie obiektowe powszechnie jest uważane za bardzo zaawansowaną technikę, to tak naprawdę jest zupełnie na odwrót! Każdy normalny człowiek myśli obiektowo. Łatwiej jest nam dzięki temu pojmować otaczającą nas rzeczywistość. Patrząc np. na komputer, większość nie widzi skomplikowanego połączenia tranzystorów i układów scalonych, tylko po prostu obiekt komputera. Programiści mają o tyle gorzej, że w początkowym etapie nauki zmuszono ich (nas) do pojmowania zagadnień informatycznych właśnie w taki, nienaturalny sposób. Programowanie proceduralne to właśnie taki sposób patrzenia na komputer - kiedy widzi się nie ekran, klawiaturę, myszkę itd. tylko osobne tranzystory i układy scalone.
W programowaniu obiektowym nie ma zmiennych globalnych ani funkcji (a przynajmniej nie powinno być!). Tutaj - jak w życiu - mamy do czynienia z zestawem obiektów, które posiadają jakieś związane z nimi własności (zmienne). Każdy obiekt udostępnia również zestaw właściwych sobie metod, które potrafią operować na własnościach tego obiektu. Część własności i metod może być nieodstępna na zewnątrz, ale może ich używać sam obiekt. Dobrą praktyką jest wystawiać do publicznej dyspozycji minimalną liczbę własności, a ustawiać i odczytywać je za pomocą metod. Dzięki temu można przeprowadzić wstępną walidację danych i upewnić się, że np. nikt nie wpisze w polu "wiek" liczby ujemnej ani żadnego tekstu jak np. "Nie podam". W przypadku PHP mamy również dostęp do metod magicznych, które pozwalają uzyskać kompromis - proste ustawianie i odczytywanie zmiennych z jednoczesną możliwością ich walidacji.
Dodatkowo programowanie zorientowane obiektowo ma coś, o czym programując proceduralnie można tylko pomarzyć - hierarchię klas. Tak samo jak w życiu możemy wyróżnić pewne abstrakcyjne pojęcia opisujące grupę przedmiotów o podobnych cechach, w programowaniu obiektowym mamy klasy. Obiekt jest natomiast konkretną instancją klasy. Na przykład odpowiednikiem klasy może być pojęcie "człowiek", a Ty jesteś już konkretnym obiektem klasy człowiek, ponieważ posiadasz imię, nazwisko, unikalne odciski palców i kod DNA. Co więcej zauważamy, że pomiędzy klasami mogą zachodzić konkretne związki. Nauczyciel, programista, malarz, stolarz, bankier to wszyscy przecież po prostu ludzie, a jednak potrzebujemy dodatkowego, bardziej szczegółowego określenia, aby ich nazwać. Co więcej, choć wszyscy oni mają jakieś cechy wspólne dla ludzi (np. wzrost, wagę, płeć) i potrafią wykonywać te same czynności (np. oddychać), to każde z nich ma zestaw dodatkowych cech, których nie posiadają inni. Np. cechą programisty może być znajomość języków programowania, a stolarz będzie potrafił zrobić meble. Mamy tu zatem do czynienia z przykładem dziedziczenia atrybutów - każdy jest człowiekiem, ale jednocześnie posiada dodatkowe umiejętności. Dzięki dziedziczeniu mamy nie tylko poukładane zmienne i funkcjonalności w naszej aplikacji, ale również ustalone jasne relacje pomiędzy klasami. Dziedziczenie pozwala również zaoszczędzić sobie pisania, ponieważ skoro każdy człowiek posiada metodę "oddychaj", to nie będziemy musieli "implementować" jej oddzielnie dla nauczyciela, programisty, malarza, stolarza czy bankiera - wystarczy że wskażemy, że dziedziczą oni atrybuty po klasie "człowiek".
Powyższe informacje są powszechnie znane osobom, które znają zasady programowania obiektowego. Nie każdy jednak zdaje sobie sprawę, że dziedziczenie to nie jedyny typ relacji mogących zachodzić między obiektami:
- Asocjacja - kiedy jeden obiekt używa drugi albo się z nim komunikuje, ale oba żyją niezależenie (programista używa komputera)
- Agregacja - obiekt nadrzędny (nie mylić z nadklasą w dziedzieczeniu) zawiera inne obiekty, jednak mogą one istnieć również samodzielnie (koło może istnieć bez samochodu)
- Kompozycja - obiekt składa się z innych obiektów, ale nie mogą one istnieć samodzielnie (ręka nie przeżyje bez reszty ciała).
Wielu programistów intuicyjnie używa tych relacji, nie zdając sobie z tego sprawy.
Wzorce projektowe
Wydawać by się mogło, że programowanie zorientowane obiektowo jest szczytem osiągnięć w zakresie implementacji systemów informatycznych. Okazuje się jednak, że na wykonanie każdej czynności jest minimum kilka sposobów, a osoby niedoświadczone nie zawsze muszą wybrać najkorzystniejszy. Z pomocą przychodzą wzorce projektowe, czyli sprawdzone rozwiązania częstych problemów informatycznych. Najbardziej znane to:
Fabryka
Jeden obiekt potrafi tworzyć różne obiekty innego typu, w zależności od warunków zewnętrznych (np. dostępnej wersji biblioteki czy systemu operacyjnego). Jest to idealne rozwiązanie, pozwalające uniknąć wstawiania if-ów w niejednokrotnie kilkudziesięciu lub kilkuset miejscach kodu.
Adapter
Kiedy mamy dwie klasy, zaimplementowane przez różnych programistów, często zdarza się, że nie potrafią one ze sobą współpracować, ponieważ nie mają zgodnych interfejsów (zestawu metod, zgodnego formatu danych). Oczywiście w takiej sytuacji można ręcznie zmienić jedną lub drugą klasę i w ten sposób uzyskać zgodność. Problem jednak pojawia się, kiedy zostanie wydana poprawka do tej klasy - wtedy musimy wszystko wykonać na nowo. Znacznie lepiej jest przygotować osobną klasę, która dopasuje interfejs jednej klasy do drugiej. Po zainstalowaniu poprawki wszystko nadal będzie działać poprawnie, pod warunkiem, że pierwotny interfejs aktualizowanej klasy nie ulegnie zmianie.
Dekorator
Zdarza się, że otrzymujemy z zewnątrz świetną klasę - prawie idealną - jednak chcielibyśmy dodać do niej lub zmienić kilka rzeczy. Podobnie jak w przypadku adaptera, moglibyśmy zmienić tę klasę, ale negatywne skutki byłby takie same. Lepiej jest udekorować kod tej klasy dynamicznie, w czasie działania programu, a nie zmieniać go fizycznie. Możemy np. stworzyć własną, niewielką klasę, która przekształci klasę bazową w taki sposób, aby nam w pełni odpowiadała.
Fasada
Jest sposobem na uporządkowanie interfejsu dużego systemu, który ma być wystawiony do użytku zewnętrznego. Często duże systemy są na tyle skomplikowane, że zewnętrznemu programiście trudno się w nich połapać - zwłaszcza, jeśli były implementowane przez wielu programistów bez odpowiedniego porozumienia.
Obserwator
Załóżmy, że po nastąpieniu jakiegoś zdarzenia chcemy wykonać jakieś czynności, ale z góry nie wiemy jakie. Można oczywiście wstawić do kodu kilka if-ów, a potem jeśli zajdzie potrzeba, dołożyć nowe, ale szukanie za każdym razem "gdzie to było?" i nieuzasadnione uzależnianie od siebie obiektów, to nie jest najrozsądniejszy pomysł. A co, jeśli kiedyś klasę, gdzie wpisaliśmy te if-y będziemy chcieli wydzielić do innego projektu, który będzie wymagał wykonania w tym miejscu innych czynność? Mnożenie wersji tej samej biblioteki powoduje poważne kłopoty w utrzymaniu kodu w przyszłości. Dokładanie kolejnych if-ów do if-ów, które zostały dołożone wcześniej do innych if-ów, sprawi że niedługo nikt nie będzie się mógł w tym połapać, co zacznie generować coraz więcej błędów i znacznie utrudni, o ile w ogóle nie uniemożliwi, dalszy rozwój aplikacji. Znacznie lepiej udostępnić w tej klasie metodę, za pomocą której wszystkie inne zainteresowane obiekty mogą się zarejestrować, że chcą dostawać powiadomienia, kiedy tylko zajdzie interesujące ich zdarzenie. Wtedy będą mogły same wykonać akcje, które są im potrzebne. Ponieważ samo zdarzenie jest generowane w jednym miejscu, nie będzie trzeba w przyszłości szukać wszystkich linii kodu, gdzie mogłoby być osobno wyzwalane, aby tam dopisać jego obsługę.
Wzorzec projektowy obserwator został zaimplementowany np. w języku JavaScript w celu obsługi zdarzeń zachodzących na stronie internetowej (metoda addEventListener).
Singleton
To sposób na zmienne globalne. Jak wspomniałem już przy okazji opisu programowania proceduralnego, ze zmiennymi globalnymi są same problemy. W dobrze napisanej aplikacji nie powinno być żadnych zmiennych globalnych! Czasem jednak potrzebujemy, aby jakaś część aplikacji była wspólna. Może to być np. obiekt konfiguracji, czy pojedyncze połączenie z bazą danych. Singleton pozwala zawsze odwołać się do dokładnie tej samej instancji obiektu w dowolnym miejscu. Najczęściej przy pierwszej takiej próbie jest tworzony pojedynczy obiekt, a następnie zapisany do zmiennej statycznej. Kolejne wywołania odczytują zapisany obiekt, a nie tworzą nowego.
Zatem kiedy następnym razem najdzie Cię ochota tworzenia zmiennej globalnej, obsługującej połączenie z bazą danych, pomyśl lepiej o singletonie.
MVC
Jest jeden szczególny rodzaj wzorca projektowego, który jest niezmiernie ważny zwłaszcza w aplikacjach internetowych. Potrafimy już pisać kod zorientowany obiektowo, znamy relacje zachodzące między obiektami oraz stosujemy wzorce projektowe. Nadal jednak brakuje nam ważnej rzeczy - szkieletu dla całej aplikacji. O ile przechodząc na obiekty pozbyliśmy się chaosu w zmiennych i funkcjach, który panował w programowaniu proceduralnym, to tak naprawdę teraz ten chaos przenieśliśmy na trochę wyższy poziom abstrakcji. Teraz same klasy mogą być dość bezładnie porozrzucane w całej aplikacji, bez jasnego przeznaczenia. Może się zdarzyć, że jedna klasa wykonuje kilka czynności, choć logicznie rzecz biorąc, są to zupełnie inne funkcjonalności. Skutecznym rozwiązaniem problemu jest zastosowanie wzorca projektowane MVC (ang. Model-View-Controller).
Zauważono, że w wielu aplikacjach - w tym niemal w każdym serwisie internetowym - można wyznaczyć trzy niezależne obszary (dodatkową zaletą płynącą z takiego podziału jest możliwość równoczesnej implementacji poszczególnych części aplikacji przez kilku programistów, bez potrzeby czekania jeden na drugiego):
Model
Zapewnia dostęp do danych aplikacji. Zwykle dane są przechowywane w bazie danych, ale modelu w żadnym razie nie należy utożsamiać z klasą dostępu do bazy danych, taką jak np. PDO lub na wyższym poziomie abstrakcji - np. PHP Doctrine (tzw. system ORM - Object Relational Mapper). Model często używa wewnętrzne takiej lub podobnej klasy, ale na zewnątrz metod, które udostępnia w żadnym razie nie może się pojawić język SQL - np. jako parametr wejściowy. Model musi ukrywać przed resztą aplikacji sposób, w jaki przechowuje dane. Dlaczego? Początkowo dane mogą być przechowywane w zwykłych plikach tekstowych. Potem możemy zdecydować się przejść na bazę danych, by w przyszłości jeszcze zmienić rodzaj bazy (niekompatybilność SQL). W szczególnym przypadku model może pobierać dane zdalne - np. zdalny kanał RSS albo web services - SOAP, REST itp. Możemy również na początku tworzenia aplikacji utworzyć metody, które zwracają dane testowe, na sztywno zapisane w kodzie metod, ale od razu w docelowym formacie. Dzięki temu pozostali programiści mogą zacząć używać takiej atrapy modelu, a w tym czasie ktoś inny po kolei będzie go implementował. Oczywiście dzięki temu przyspieszymy czas powstawania dużych aplikacji.
Dzięki nieujawnianiu na zewnątrz szczegółów implementacji, bez problemu będziemy mogli wymienić model, nie zmieniając reszty aplikacji. Wystarczy tylko, aby nowa wersja udostępniała takie same metody i zwracała dane w takim samym formacie. Niebagatelne znaczenie ma tutaj ustalenie spójnego formatu zwracanych danych, ponieważ pozwoli nam to później m.in. uniknąć pisania kilku wersji tego samego widoku tylko dlatego, że format danych zwracanych z różnych metod, nie jest identyczny, choć ich charakter jest ten sam.
Model nie może:
- Przyjmować jako parametry zapytania ani fragmentów zapytań SQL
- Dokonywać wstępnej wizualizacji, tzn. np. generować kodu HTML
- Używać widoków ani kontrolerów
- Pobierać dane w ten lub inny sposób, w zależności od logiki aplikacji
- Samodzielnie wywoływać widoki
- Wywoływać kontroler
W przypadku modelu zwykle mamy do czynienia z jednym z dwóch wzorców projektowych:
- DAO/DVO - (ang. Data Access Object/Data Value Object) jest to zestaw klas (DAO) udostępniających metody, które pozwalają spełnić zadane w aplikacji funkcjonalności. Zdarza się, że metody klas DAO zwracają po prostu tablice asocjacyjne, ale znacznie lepiej jest, aby zwracały obiekty (DVO), ponieważ dzięki temu można dla nich zaimplementować osobne metody oraz sterować widocznością własności.
- Active Record - w tym wzorcu sam model jest obiektem, na którym potem operuje kontroler i którego dane wyświetla widok. Posiada zwykle specjalne metody load i save - najpierw można załadować dane obiektu z bazy (load), zmienić je i z powrotem zapisać w bazie (save). Pewną komplikacją jest jednak wykrywanie aktualnego stanu obiektu (nowy, wczytany, zmodyfikowany, zapisany) oraz pobierania listy obiektów (np. listy artykułów z danej kategorii) - do tego zwykle stosuje się metody statyczne, co nie do końca jest intuicyjne.
Widok
Zajmuje się prezentacją danych dla użytkownika. Sam nie potrafi pobierać danych (tym zajmuje się model) ani nie wie, z jakiego modelu powinien skorzystać. Jedyne co robi, to przyjmuje dane w określonym formacie i potrafi je wyświetlić. Widoku nie należy utożsamiać z systemem szablonów takim jak np. Smarty, choć często taki czy inny system szablonów jest wykorzystywany przez widok. Przede wszystkim widoków w jednej aplikacji może być kilka i nie chodzi tutaj o tzw. "skórki" znane z serwisów internetowych. Jeden widok może generować kod HTML, inny - XML (np. RSS), a jeszcze inny dokumenty PDF czy RTF - wystarczy aby każdy z nich posiadał taki sam interfejs.
Widok nie może:
- Pobierać danych (zwłaszcza wykonywać zapytań SQL)
- Samodzielnie wywoływać modele
- Stosować bardziej złożonej logiki, poza prostą logiką konieczną do wyświetlenia danych w określony sposób
- Wywoływać kontroler
Kontroler
Kontroler jest mózgiem aplikacji. To on i tylko on wie, jakie modele wywołać i do których widoków przekazać dane przez nie zwrócone. To w nim i tylko tam znajduje się cała logika aplikacji, czyli potocznie mówiąc wszystkie if-y sterujące przebiegiem działania programu. Sam jednak nie potrafi pobrać danych (używa do tego celu modelu) ani ich wyświetlić (to robi widok). Jest podzielony na akcje, które stanowią odpowiedź na konkretne żądanie użytkownika. W aplikacjach okienkowych akcją może być np. "Otwórz plik", "Zapisz plik" itd. W przypadku serwisów internetowych akcją jest najczęściej wyświetlenie strony określonego rodzaju - np. z treścią wybranego przez użytkownika artykułu.
Kontroler nie może:
- Samodzielnie pobierać danych
- Samodzielnie wyświetlać danych
|
_________________  Użytkownik otrzymał punkt pomocy za ten post od: Dealis, miodan, SkyMan, T4KEDA |
|