Witaj, Gościu O nas | Kontakt | Mapa
Wortal Forum PHPEdia.pl Planeta Kubek IRC Przetestuj się!

Pomysły, porady, sugestie - dobre nawyki. - Po co nam te obiekty - cz. 1

Wstęp

W życiu każdego prawdziwego mężczyzny (i podobno niektórych kobiet) nadchodzi taki dzień kiedy postanawia nauczyć się programować obiektowo. Jak to zwykle bywa początki są trudne. Trudno przestawić się po x latach pisania strukturalnie. Wszystko wydaje się jakąś fanaberią wymyśloną na siłę nie wiadomo nawet po co. Tyle tytułem, i tak zbyt długiego, wstępu.

Bardzo lubię jak coś jest poparte dobrym przykładem, a jako, że jestem autorem tego artykułu to tutaj tak właśnie będzie. Każda średnio inteligentna forma życia przed rozpoczęciem nauki czegokolwiek zada sobie pytanie: po co mi to potrzebne? W tym przypadku odpowiedzią jest Twoja własna wygoda. Ten artykuł skupia się właśnie na zaletach korzystania z OOP z praktycznego punktu widzenia. Nie uświadczysz tu gotowych listingów, a większość terminów jest potraktowana po macoszemu. Sam będziesz musiał wyszukać sobie w sieci szczegółów i definicji, ale jako programista warto zżyć się z wujkiem Google.

Pierwszy obiekt

A więc robimy serwis gdzie każdy użytkownik ma skarbonkę i może dorzucić do niej parę groszy. Strukturalnie wygląda to tak, że łączymy się z bazą, piszemy zapytanie SQL, które zwróci nam obecny stan skarbonki, dodajemy sumę wrzuconą przez użytkownika i zapisujemy nową wartość do bazy. Tak jak w przykładzie z samochodem nas nie powinno interesować w naszej aplikacji jak skarbonka wykonuje te operacje. My chcemy po prostu coś do niej dodać. Podsumowując: logiką aplikacji jest dodanie tam pieniędzy i nie ma powodu, żeby mieszać logikę razem z techniczną realizacją tych czynności (tak jak w samochodzie). Co nas (z punktu logiki aplikacji) obchodzi skąd skarbonka wie ile jest w niej pieniędzy, czy trzyma to w bazie czy w szafce, albo w jaki sposób zapisuje te dane? To nie jest istotne. W naszej aplikacji chodzi o to, żeby do skarbonki została dodana pewna suma i to tyle.

$piggybank = new Piggybank("Adam");
 $piggybank->add( 5 );

Obiektowo tak to będzie wyglądało. I to wszystko. To wszystko co nas interesuje w logice (czyli sposobie działania) naszej aplikacji. Jak już wspominałem, mamy gdzieś tak naprawdę co ta skarbonka robi z tymi danymi. Jak je zapisuje i gdzie. Chcemy móc dodać tam coś, móc sprawdzić ile obecnie jest i ewentualnie wybrać troszkę sianka na własne wydatki. Każdy user ma swoją prywatną skarbonkę. My akurat dorzuciliśmy 5 groszy Adamowi. Wszystko ładnie i wspaniale, ale jak to działa? Spokojnie, dalej nieco się wyjaśni!

new Piggybank("Adam");

Widać jak na dłoni, że przekazujemy do obiektu na dzień dobry jakiś parametr. ( Konstruktor nie jest wymagany, a nawet jeśli go zaimplementujemy nie musi przyjmować żadnych parametrów). Ten właśnie parametr dostaje się do konstruktora. Konstruktor dzięki niemu wie co ma robić, w tym wypadku przekazujemy login właściciela skarbonki. Dlatego nasza skarbonka będzie wiedziała kto jest jej właścicielem i komu należą się pieniążki do niej wrzucone. Dość oczywiste, prawda? Więc co robi nasz konstruktor? Przypisuje sobie do prywatnej właściwości (właściwość - zmienna w obiekcie) login właściciela.

Dane dotyczące stanu naszych skarbonek trzymamy w tabeli w bazie danych. Nasza tabela więc będzie zawierała dwie kolumny: nazwa skarbonki (taka sama jak login właściciela) i transakcja (wpłata jakiejś sumy lub wypłata). Dalej (w przykładzie) zażyczyliśmy sobie, żeby do skarbonki Adama zostało wrzucone 5gr. Wywołaliśmy metodę add(), a jako parametr podaliśmy rzeczone 5 groszy. Co robi metoda add()? Ano korzysta z prywatnej (czyli takiej do której mamy dostęp tylko z wnętrza obiektu, a metoda add() jest jak najbardziej we wnętrzu obiektu) właściwości gdzie zapisaliśmy login właściciela i buduje zapytanie do bazy, które doda po prostu rekord.

Załóżmy, że użytkownik Krzyś chce przekazać ze swojej skarbonki 20 groszy Adamowi. Składnia wyglądałaby mniej więcej tak:

$piggybank_krzys = new Piggybank("Krzys");
 $piggybank_adam =  new Piggybank("Adam");
 if( $piggybank_krzys->withdraw( 20 ))  $piggybank_adam->add( 20 );

Prawda, że proste? Na obiekcie Krzysiowej skarbonki wykonaliśmy metodę withdraw (wypłać) i jako parametr podaliśmy sumę. Całość jest zamknięta w warunku jako, że wypłata może się zakończyć niepowodzeniem. Krzyś może nie mieć na przykład wystarczająco grosików w skarbonce. Wtedy metoda zwróci nam false, czyli wiem, że nie udało się wypłacić, a tym samym nie możemy dać tych pieniążków Adamowi. W tym przykładzie widać jeszcze jedną ciekawą sprawę. Możemy mieć wiele obiektów tej samej klasy. Mamy dwie skarbonki (Adama i Krzyśka), które są obiektami klasy Skarbonka. Jednak są one niezależne. Dane trzymane w nich są bezpieczne. To tak jak $a i $b mogą być danymi typu tablica, ale to jedyne co je łączy. Zmiany jednej nie wpływają na drugie. Po prostu tworząc obiekty tworzymy swoje struktury danych, które przechowują to co chcemy, tak jak chcemy, a dodatkowo możemy wykonywać na nich pewne akcje.

Dobra, lecimy dalej. Nasi użytkownicy skarżą się, że nie wiedzą ile mają w skarbonce. Taki w sumie urok skarbonki jednak my w swoich obiektach możemy łamać takie ograniczenia. Potrzebujemy metody, która zwróci nam stan skarbonki.

$piggybank = new Piggybank( "Adam" );
 $balance = $piggybank->getBalance();

I tym oto sposobem w zmiennej $balance mamy stan "konta" naszego Adama. Bez pięciu parametrów przekazywanych do jakiejś dziwnej funkcji. Po prostu getBalance(); i jest. Metoda getBalance(); po prostu sumuje wszystkie wpłaty (wypłata to wpłata minusowej wartości) tam gdzie właścicielem jest Adam (co zapisał nam konstruktor w prywatnej właściwości).

Sukces goni sukces

Nasz serwis się rozwija szybciej niż Nasza-Klasa. Robimy promocję dla nowych userów. Każdy kto założy sobie u nas skarbonkę dostanie przy każdej wpłacie dodatkowo 10% (wpłacanej sumy) od nas. Musimy przerobić metodę add(), w taki sposób, żeby nam dodawała 10%. Ale chwila, chwila! Promocja ma dotyczyć tylko nowych użytkowników, a my mamy w naszym kodzie setki wywołań metody add(). Musielibyśmy wszystko zmieniać. Możemy temu zaradzić na dwa sposoby. Możemy dodać do naszej klasy jakąś właściwość, która będzie określała czy użytkownik jest objęty promocją czy nie. Oczywiście konstruktor sam może to sobie sprawdzać. Jednak naszej skarbonki nie powinno to interesować czy użytkownik jest nowy czy nie. Po co zaśmiecać nasza klasę? To jest skarbonka, a nie sprawdzacz_czy_user_jest_w_promocji, poza tym działa ona bardzo fajnie i tak powinno zostać. Innymi słowy: nie chcemy nic tam zmieniać!

Robimy sobie nową klasę PromoPiggybank, która dziedziczy po Piggybank. Dziedziczenie polega na tym, że wszystkie (nie licząc prywatnych) metody i właściwości,które zdeklarowaliśmy w rodzicu (tutaj Piggybank) są również dostępne w nowej klasie. Dodatkowo możemy nadpisać niektóre metody, nam konkretnie zależy na nadpisaniu metody add(). Jedyne co musimy zrobić to stworzyć metodę add od nowa w naszej klasie potomnej (PromoPiggybank). Nie oznacza to jednak, że naszą starą metodę add() bezpowrotnie tracimy! Możemy ją nawet tu wykorzystać.

function add( $amount ){
	$amount = $amount + floor( $amount / 10 );
	return parent::add( $amount );
}

Widać czarno na białym, że jedyne co robi nasza nowa metoda add() (ta z klasy PromoPiggybank) to zmienna wpłacaną sumę o 10% i wywołuje naszą starą metodę add() z rodzica czyli klasy Piggybank. Zero nadmiarowości kodu w tym wypadku (pomijając operator dodawania zastosowany dla zwiększenia czytelności).

UWAGA! W klasie Piggybank określiliśmy właściwość przechowującą login właściciela jako prywatną. W związku z czym nie będzie ona dostępna w klasie dziedziczącej. Jedyne co musimy zrobić to zamienić ją na chronioną (protected)

Obiektów całe mnóstwo

Łatwo zauważyć, że użytkownik to również obiekt. Posiada login, imię, nazwisko, skarbonkę... Ba, powinniśmy nawet od niego zacząć, ale jako, że klasa Piggybank jest prostsza w zrozumieniu logiki OOP zaczęliśmy od niej. Tak więc możemy stworzyć sobie obiekt user. Konstruktor tak jak skarbonka dostanie jako parametr login użytkownika i pobierze sobie z bazy resztę. Konstruktor przypisze także do publicznej właściwości(takiej dostępnej spoza obiektu) obiekt skarbonki. Obiekty bowiem mogą składać się z innych obiektów.

$user = new User("Adam");
 $user->piggybank->add( 5 );

Kurde, ale skąd my wiemy, że zalogowanym użytkownikiem jest Adam? Ano trzymamy w sesji sobie jego imię. Ale jak to się stało, że te dane znalazły się w sesji. Mamy funkcję login(); która to robi? Bardzo niedobrze. To klasa User powinna zajmować się logowaniem. użytkowników. W OOP jest coś takiego jak metody statyczne. Co to za cudo? Metod statycznych możemy używać nawet jeśli nie mamy obiektu danej klasy. Trudno zresztą mieć obiekt klasy User konkretnego użytkownika jak nie wiemy o jakiego usera chodzi, prawda? Spójrzmy na kod:

$user = User::login( $login, $password);
 $user->piggybank->add( 5 );

User::login() to po prostu wywołanie statycznej metody login(); Metoda ta wymaga dwóch parametrów. Loginu i hasła. Sprawdza sobie w bazie czy mamy takiego użytkownika. Jeśli mamy to zapisuje dane do sesji i zwraca obiekt User (np new User("Adam")), jeśli nie mamy zwraca false; I już mamy dwa fajne obiekty i do tego jeden jest częścią drugiego. User zawiera w sobie Piggybank. Widać jak tego miło używać? $user->piggybank->add( 5 ); i już. Nie interesujemy się jak poszczególne obiekty przechowują te dane i skąd je mają. Po prostu ich używamy.

Ale chwila chwila! Miała być promocja jakaś, gdzie jest moje 10%!? Możemy łatwo uniknąć oskarżeń o złodziejstwo. Pamiętacie skąd obiekt user ma w sobie skarbonkę? Konstruktor ją tworzy i przypisuje do prywatnej właściwości $piggybank. Tak więc konstruktor klasy User, musi najpierw sprawdzić datę rejestracji użytkownika. Jeżeli jest to mniej niż miesiąc od dzisiaj tworzy jako skarbonkę obiekt klasy PromoPiggybank, jeżeli więcej to nasze zwykłe, stare sprawdzone Piggybank. Dla nas jest to przezroczyste. Używamy sobie tych obiektów jakby nigdy nic, one same wiedzą jak mają się zachować. Jeśli zrezygnujemy z promocji nie zmieniamy dziesiątek linii kodu, tylko zmieniamy konstruktor klasy User tak by zawsze przypisywał do zmiennej zwykłą skarbonkę (Piggybank).

Klasa User powinna również umożliwić zmianę imienia czy nazwiska, a także wylogowanie użytkownika. Za jej pomocą możemy dać użytkownikowi np bana! Czyż to nie brzmi wspaniale? No dobra, ale co mam zrobić jak ktoś chce się zarejstrować dopiero?

$user = new User(); 
 $user->login = "baltazzz";
 $user->firstname = "Baltazar";
 $user->surname = "Gąbka";
 $user->password = "baltazzz";
 $user->save();

Wygląda bosko, co? Ale jak te czary działają? Nasz konstruktor nie dostał żadnego parametru, w związku z czym wie, że chodzi nam o nowego użytkownika. Nie przypisuje skarbonki, ani nie pobiera danych usera. Przypisuje za to do specjalnej prywatnej właściwości, że jest to całkiem nowy użytkownik. Następnie przypisujemy do publicznych właściwości login, imię, nazwisko i hasło. Teraz najciekawsze, wywołujemy metodę save(); Metoda save() sprawdza sobie prywatną właściwość i dowiaduje się, że jest to całkowicie nowy użytkownik. Na podstawie właściwości (login, firstname, surname, password) tworzy zapytanie sql. Dodatkowo automatycznie może dodać czas rejestracji. I mamy dane zapisane. Jak zwykle to sam obiekt user martwi się gdzie zapisać te dane. My chcemy mieć tylko wygodny do nich dostęp. A co jeśli nie podamy np hasła? Jak można zarejestrować użytkownika bez hasła? Metoda save() powinna to sprawdzić zanim doda do bazy. Nas to nie interesuje z zewnątrz.

Problemy z użytkownikami

Ehh, same problemy! Pan Baltazar okazał się bardzo nierozsądny używając swojego loginu jako hasła. Zreflektował się na szczęście w porę i chce sobie to hasło zmienić. Co robimy?

$user = new User("baltazzz");
 $user->password = "baltazzz11";
 $user->save();

Konstruktor dostał login naszego użytkownika. Szuka więc jegomościa w bazie i pobiera rekord z jego danymi. Przypisuje do swoich właściwości resztę danych (imię, nazwisko, hasło), następnie ustawia flagę informując, że to jest użytkownik istniejący już w bazie. My zmieniamy właściwość zawierającą hasło i wywołujemy metodę save(); Save już wie, że to nasz dobry stary znajomy i nawet nie myśli przez chwilę, żeby robić INSERT do bazy tak jak poprzednio. Zamiast tego robi UPDATE istniejącego rekordu. Teraz Pan Baltazar może czuć się bezpiecznie, jego hasło zostało zmienione. Jeżeli już jesteśmy przy bezpieczeństwie to w logice aplikacji nie interesuje nas jak te dane są przechowywane. Od tego jest klasa User w tym przypadku. To ona powinna zastosować środki ostrożności jak na przykład zakodować hasło przed zapisaniem do bazy. Dla nas jednak jest to przezroczyste. Jeśli zmienimy sposób kodowania hasła, zmieniamy to tylko w jednej metodzie. Reszta kodu zostaje bez zmian.

O to chodzi w obiektach. Taki magnetofon jest obiektem. Udostępnia takie metody jak PLAY czy STOP. Mnie osobiście średnio interesuje jak on to robi (jak byłem mały to mnie to strasznie interesowało). Naciskam PLAY i ma grać. Tak właśnie działają obiekty. Udostępniają pewne metody, z których możemy skorzystać bez interesowania się jak to jest technicznie wykonywane.

Wracamy jednak do rzeczywistości. Baltazar staje się nieznośny! Tym razem wymyślił sobie, że chce zmienić swój login! To już jakaś paranoja! Nie można zmieniać loginu! Co sprytniejsi pewnie już zaprzęgają metodę save() do sprawdzania czy login się nie zmienił. Można i tak, ale jest lepszy sposób. Możemy sobie napisać tzw metody dostępowe. Jak to wygląda w praktyce? Zrobimy to na przykładzie publicznej właściwości $login. Na początek zmieniamy tą właściwość z publicznej na prywatną. Czyli nie mamy do niej dostępu z zewnątrz. Tylko sam obiekt, do którego ona należy może ją modyfikować czy w ogóle czytać. Jak więc się do niej dostać? Robimy sobie publiczną metodę setLogin(), która było nie było należy do obiektu.

$user = new User("baltazzz");
 $user->setPassword("baltazzz11");
 $user->setFirstname("Balbinka");
 $user->save();

Jak już wiemy konstruktor dostając login użytkownika zapisuje sobie w prywatnej właściwości, że mamy do czynienia z istniejącym użytkownikiem. Tak więc metoda setLogin(); nie wprowadzi żadnych zmian! Zadziała tylko jeśli zakładamy sobie nowego użytkownika. Nie można zmieniać loginu! Dodatkowo metoda ta powinna sprawdzać czy nowy login nie jest czasem już zajęty przez innego użytkownika. Tak więc nie dość, że obiekty pozwalają nam zachować przejrzystość, skupić się na sposobie działania (logice) aplikacji to jeszcze nie raz uchronią nas przed katastrofą.

Zrobiliśmy już z naszym Baltazarem (a może z Balbinką) chyba wszystko. Zarejestrowaliśmy go, zmienialiśmy imię, wpłaciliśmy mu parę złotych do skarbonki... Wszystko było kwestią jednej linijki. Pomyśl sobie jakby wyglądał Twój kod strukturalnie! Wyobraź sobie funkcję register($login, $name, $surname, $password); Cztery parametry!? I ja mam pamiętać jakie i w jakiej kolejności? A może bez funkcji prosto z POSTa? Cudownie SQL wymieszany z PHP i HTMLem jest naprawdę smakowity! Już nie mogę się doczekać modyfikacji kodu za pół roku! Sama przyjemność! Tym optymistycznym akcentem chciałbym zakończyć ten "artykuł".

W artykule celowo nie ma przykładowych listingów. W OOP nie chodzi o sposób implementacji tylko o zaprojektowanie obiektów. Sama składnia klas jest dostępna w internecie w 1000 miejsc więc nie będę tego kopiował. Artykuł miał wyjaśnić jakie są korzyści ze stosowania OOP osobom całkowicie zielonym. Wiem po sobie, że jak zaczynałem to przykłady, że piesek dziedziczy po ssaku na niewiele się przydają w praktyce. W artykule zostało wiele pominięte, żeby nie mącić w głowie. O tym, że te klasy nie są idealne, a także dlaczego nie są dowiemy się już w następnej części. Mało tego, dowiemy się również jak je poprawić i zbudujemy sobię warstwę abstrakcji (prawda, że fajnie brzmi) dla bazy danych. Owocnej nauki.

Wasze opinie
Wszystkie opinie użytkowników: (0)
Mentax.pl    NQ.pl- serwery z dodatkiem świętego spokoju...   
O nas | Kontakt | Mapa serwisu
Copyright (c) 2003-2024 php.pl    Wszystkie prawa zastrzeżone    Powered by eZ publish Content Management System eZ publish Content Management System