Programowanie obiektowe w PHP 5 cz. 3

Autor: Tomasz Jędrzejewski
Data publikacji: 02.01.2005, 16:56 | Ostatnia modyfikacja: 19.11.2006, 17:36

Trzecia część artykułu o programowaniu obiektowym w PHP 5 poświęcona jest interfejsom, iteratorom oraz wykorzystaniu wszystkiego w praktyce.



Trzecia część artykuÅ‚u o programowaniu obiektowym w PHP 5 poÅ›wiÄ™cona jest prawie w caÅ‚oÅ›ci interfejsom oraz tworzonym przy ich pomocy iteratorom. Ponadto omówimy sobie zagadnienie serializacji obiektów, porównamy OOP w PHP 4 z tym z "piÄ…tki" oraz spróbujemy przedstawić praktyczne wykorzystanie zdobytej wiedzy. A wiÄ™c zaczynamy.

Interfejs - z czym to siÄ™ je?

Interfejsem nazywamy zbiór nagÅ‚ówków metod (czyli bez konkretnej zawartoÅ›ci), który mogÄ… wykorzystać klasy do realizacji wÅ‚asnych celów. ZaÅ‚óżmy, że mamy sobie jakiÅ› tam algorytm, który dziaÅ‚a z obiektami dowolnych klas, pod warunkiem, że posiadajÄ… one metody X, Y oraz Z. MoglibyÅ›my stworzyć tutaj abstrakcyjnÄ… klasÄ™ bazowÄ…, ale wtedy uniemożliwilibyÅ›my programistom dziedziczenie z innych klas. Tu na scenÄ™ wkraczajÄ… interfejsy. Tworzymy sobie taki jeden z nagÅ‚ówkami żądanych metod. Jeżeli programista zażyczy sobie możliwoÅ›ci użycia jego klasy w naszym algorytmie, po prostu doda do jej deklaracji ciÄ…g implements NaszInterfejs i po sprawie. Algorytm natomiast może przy użyciu znanego nam już instanceof sprawdzić, czy nasz interfejs rzeczywiÅ›cie jest implementowany.

<?php
 
   interface nasz_interfejs{
      public function zwroc_id_obiektu();
   }
 
   class nasza_klasa implements nasz_interfejs{
 
      public function __construct(){
         echo 'Tworzymy `nasza_klasa`<br/>';
      } // end __construct();
 
      public function zwroc_id_obiektu(){
         return (int) substr((string)$this, 11, strlen((string)$this) - 11);
      } // end zwroc_id_obiektu();
 
   }
 
   class nasza_inna_klasa implements nasz_interfejs{
 
      public function __construct(){
         echo 'Tworzymy `nasza_inna_klasa`<br/>';
      } // end __construct();
 
      public function zwroc_id_obiektu(){
         return (int) substr((string)$this, 11, strlen((string)$this) - 11);
      } // end zwroc_id_obiektu();
 
   }
 
   class jakas_klasa{
 
      public function __construct(){
         echo 'Tworzymy `jakas_klasa`<br/>';
      } // end __construct();
 
   }
 
   function algorytmus(){
      $args = func_get_args();
      $suma = 0;
      foreach($args as $arg){
         if($arg instanceof nasz_interfejs){
            $suma += $arg -> zwroc_id_obiektu();
         }else{
            echo 'Ostrzeżenie: Wprowadzono klase nieobslugujaca interfejsu `nasz_interfejs`!<br/>';
         }
      }
      return $suma;
   } // end algorytmus();
 
   $obj1 = new nasza_klasa;
   $obj2 = new nasza_klasa;
   $obj3 = new nasza_inna_klasa;
   $obj4 = new jakas_klasa;
 
   echo 'Suma ID obiektow wynosi '.algorytmus($obj1, $obj2, $obj3, $obj4).'<br/>';
?>

PrzykÅ‚ad ten można zaliczyć do tzw. mocno alternatywnych, gdyż liczy... sumÄ™ ID obiektów implementujÄ…cych interfejs nasz_interfejs :). Mamy sobie trzy klasy, z czego dwie implementujÄ… to nasze cudeÅ„ko. Tworzymy cztery obiekty, wprowadzamy je do algorytmu umieszczonego w funkcji algorytmus(). Ten, wykorzystujÄ…c instanceof sprawdza, czy można wywoÅ‚ać naszÄ… metodÄ™ zwroc_id_obiektu(). To caÅ‚a filozofia.

Interfejsy majÄ… nad klasami abstrakcyjnymi jeszcze jednÄ… przewagÄ™ - dana klasa może ich implementować kilka naraz, podczas gdy normalnie byÅ‚aby ograniczona do jednej abstrakcyjnej. Nazwy poszczególnych interfejsów oddzielamy przecinkami:

class klasa implements interfejs1, interfejs2, interfejs 3

Do góry

Obiekty a pętla foreach

NowoÅ›ciÄ… w PHP 5 jest możliwość potraktowania obiektów pÄ™tlÄ… foreach. Zwracane sÄ… wtedy wartoÅ›ci wszystkich ich publicznych pól, włącznie z nazwÄ…. Jest to przydatne zwÅ‚aszcza wtedy, gdy pola tworzymy dynamicznie.

<?php
 
   class magazyn{
      private $pole_pryw = 24;
 
      public function dodaj_pole($nazwa, $wartosc){
         // dynamiczne tworzenie pola - jak zwykla zmienna
         $this -> {$nazwa} = $wartosc;
      } // end dodaj_pole();
 
   }
 
   $magazyn = new magazyn;
   $magazyn -> dodaj_pole('pole1', time());
   $magazyn -> dodaj_pole('pole2', rand(0,100));
   $magazyn -> dodaj_pole('pole3', 13);
 
   foreach($magazyn as $nazwa => $wartosc){
      echo $nazwa.' => '.$wartosc.'<br/>';
   }
?>

Zauważ, że zawartość pola $pole_pryw zdefiniowanego jako prywatne, nie zostaÅ‚a zwrócona.

To jednak nie wszystko... PHP 5 daje nam możliwość peÅ‚nej manipulacji tym, co zwróci obiekt potraktowany pÄ™tlÄ… foreach! Jak? Przy pomocy dwóch predefiniowanych interfejsów - Iterator oraz IteratorAggregate...

Do góry

Piszemy iterator

ObsÅ‚ugÄ… iteracji w klasie X zajmujÄ… siÄ™ interfejsy Iterator, IteratorAggregate oraz specjalna klasa pomocnicza. CaÅ‚a sztuczka polega na tym, że implementujemy w tejże klasie pierwszy z interfejsów. Dostarcza on metody w stylu rewind(), next() itd. pozwalajÄ…cych przesuwać siÄ™ po rekordach i pobierać ich zawartoÅ›ci. NastÄ™pnie we wÅ‚aÅ›ciwej klasie implementujemy drugi interfejs. Mamy dziÄ™ki temu dostÄ™p do jednej metody - getIterator(), której zadaniem jest utworzenie obiektu klasy pomocniczej obsÅ‚ugujÄ…cej pÄ™tlÄ™. Może brzmi to skomplikowanie, ale w ten sposób kilka klas może Å‚atwo korzystać z tego samego iteratora. Możemy np. stworzyć klasÄ™ iteratora do obsÅ‚ugi pobierania przetworzonych danych. NastÄ™pnie kilka klas obrabiajÄ…cych te dane użyje jej i tak ominiemy konieczność powtarzania tego samego kodu do obsÅ‚ugi pÄ™tli dla np. 20 klas.

Praktycznie pokażę to na podstawie obiektowej nakładki na funkcję file(). Wykorzystywany przez nią iterator użyję następnie w drugiej klasie.

<?php
 
   class nasz_iterator implements Iterator{
      private $i;
      private $obj;
 
      public function __construct($obiekt){
         $this -> obj = $obiekt;
         $this -> i = 0;
      } // end __construct();
 
      public function rewind(){
         $this -> i = 0;
      } // end rewind();
 
      public function valid(){
         return $this -> obj -> key_exists($this -> i);
      } // end valid();
 
      public function key(){
         return $this -> i;
      } // end key();
 
      public function current(){
         return $this -> obj -> get_item($this -> i);
      } // end current();
 
      public function next(){
         $this -> i++;
      } // end next();
   }
 
   class x_file implements IteratorAggregate{
      protected $plik;
 
      public function __construct($plik){
         if(file_exists($plik)){
            $this -> plik = file($plik);
         }else{
            $this -> plik = array(0 => '');
         }
      } // end __construct();
 
      public function getIterator(){
         return new nasz_iterator($this);
      } // end getIterator();
 
      public function get_item($i){
         return trim($this -> plik[$i]);
      } // end get_item();
 
      public function key_exists($i){
         return isset($this -> plik[$i]);
      } // end key_exists();
   }
 
   class x_list extends x_file{
      public function __construct(){
         $this -> plik = array();
      } // end __construct();
      
      public function add($item){
         $this -> plik[] = $item;
         return count($this -> plik) - 1;
      } // end add();
   }
 
   $plix = new x_file('test.txt');
   foreach($plix as $lnum => $linia){
      echo $lnum.' - '.$linia.'<br/>';
   }
 
   $lista = new x_list;
   $lista -> add('pozycja 1');
   $lista -> add('pozycja 2');
   $lista -> add('pozycja 3');
 
   echo '<br/>';
 
   foreach($lista as $id => $pozycja){
      echo $id.' - '.$pozycja.'<br/>';
   }
?>

Przyjrzyjmy siÄ™ najpierw klasie nasz_iterator. Do czego sÅ‚użą poszczególne metody? Oto ich opis:

rewind()
Ustawia iterator na pierwszym elemencie do pobrania

valid()
Sprawdza, czy ID aktualnej pozycji jest poprawny.

key()
Zwraca ID aktualnej pozycji

current()
Zwraca wartość aktualnej pozycji

next()
Przesuwa wskaźnik na następną pozycję

Z kolei w klasach korzystajÄ…cych z iteratora mamy jednÄ… metodÄ™: getIterator(). Musi ona zwrócić obiekt iteratora, którego chcemy użyć. PHP nie może go sobie utworzyć samodzielnie, ponieważ:

Przyjrzyj siÄ™ dobrze iteratorowi i klasie x_file(). Obie wspóÅ‚pracujÄ… ze sobÄ… poprzez udostÄ™pnienie odpowiednich metod do sprawdzania poprawnoÅ›ci i przetwarzania poszczególnych elementów tablicy. Iterator nie tylko sucho prosi: podaj mi referencjÄ™ do tablicy, a ja już siÄ™ zajmÄ™ wszystkim. On chciaÅ‚by dostać referencjÄ™ do samego obiektu. Prosi: daj mi siebie :) i przygotuj dla mnie metody do sprawdzania poprawnoÅ›ci ID i przetwarzania danych do pobrania - być może przy ich pobieraniu bÄ™dziesz chciaÅ‚ coÅ› w nich zmodyfikować?

Widzimy wiÄ™c, że takie podejÅ›cie do sprawy daje nam dużą swobodÄ™ - możemy dowolnie eksperymentować ze wspóÅ‚pracÄ… pomiÄ™dzy iteratorem, a klasami tak, by uzyskać jak najwiÄ™kszÄ… elastyczność. Widać to z resztÄ… na przykÅ‚adzie - nasz_iterator chciaÅ‚by mieć dostarczane metody, które obrobiÄ… dane za niego. W ten sposób bÄ™dzie go można również wykorzystać z innÄ… klasÄ…, pragnÄ…cÄ… jeszcze inaczej przetwarzać informacje.

Dodatkowo przykÅ‚ad ujawniÅ‚ ciekawÄ… cechÄ™ interfejsów - jeÅ›li klasa jest pochodnÄ… od klasy implementujÄ…cej jeden z nich, sama też go implementuje. U nas jest to IteratorAggregate - pomimo, że w x_list wcale nie zadeklarowaliÅ›my żadnego interfejsu do implementacji, nie utworzyliÅ›my żadnej z jego metody, on również poprawnie dziaÅ‚a z pÄ™tlÄ… foreach, gdyż wszystko sobie już odziedziczyÅ‚.

Do góry

Rzutowanie typów

Jak wiadomo, zmienne w PHP nie majÄ… przypisanego na staÅ‚e żadnego typu. Programowanie obiektowe w wersji 5 wprowadza jednak pewnÄ… furtkÄ™ - jako, że jest caÅ‚kowicie niezależne od strukturalnego modelu, możemy w pewnych sytuacjach okreÅ›lać typ parametrów wprowadzanych do metod! To dobra wiadomość. ZÅ‚a jest taka, że można to zastosować wyłącznie do obiektów, a mianowicie do sprawdzenia, czy wprowadzony obiekt jest instancjÄ… jakiejÅ› konkretnej klasy/interfejsu. Zapis wyglÄ…da w ten sposób:

function funkcja_lub_metoda(jakas_klasa $parametr){
    // tresc
}

W ten sposób okreÅ›liliÅ›my, że wprowadzony parametr musi być obiektem klasy jakas_klasa. W praktyce zapis ten jest skrótem od czegoÅ› takiego:

function funkcja_lub_metoda($parametr){
   if(!($parametr instanceof jakas_klasa)){
      die('Nieprawidłowy typ zmiennej $parametr');
   }
   // dalsza tresc
}

Tak wiÄ™c zostaÅ‚o to wprowadzone dla uÅ‚atwienia życia programistom i skrócenia kodu.

Do góry

Zmienne obiektowe cd.

W pierwszej części serii wspomniaÅ‚em, że powrócimy jeszcze do tego, że zmienna obiektowa jest jedynie referencjÄ… do obiektu. Co tu jednak jest jeszcze do pisania? Ano, dużo. Przede wszystkim - zwracanie obiektów poprzez instrukcje return nie stanowi żadnego problemu. Możemy zatem pokusić siÄ™ o stworzenie czegoÅ›, co zwie siÄ™ fabrykÄ… klas. Jest to zwyczajna funkcja/metoda, pozwalajÄ…ca nam tworzyć obiekty od różnych klas. Dodatkowo wykonywać może ona za nas rozmaite dodatkowe czynnoÅ›ci zwiÄ…zane z inicjacjÄ…. RozwiÄ…zanie to możemy wykorzystać np. podczas pisania systemu moduÅ‚ów:

   function fabryka_modulow($modul){
      static $call;
      if(file_exists('./moduly/'.$modul.'.php')){
         require_once('./moduly/'.$modul.'.php');
 
         $m = new $modul;
 
         if($m instanceof modul_interface && $call != 1){
            $m -> set_action($_GET['cmd']);
            $call = 1;
         }
         return $m;
      }
      return modul_domyslny;
   } // end fabryka_modulow(); 

Używać tego można w ten sposób:

   $news = fabryka_modulow('news');
   $artykuly = fabryka_modulow('artykuly');

Ze zwracaniem obiektu przez funkcje/metody związana jest jeszcze jedna ciekawa sztuczka. Oto, co można zrobić przy odrobinie chęci:

$zwierzyniec -> zwroc_zwierze(15) -> daj_glos();

Taki Å‚aÅ„cuszek można nadal rozszerzać. Problem pojawi siÄ™, gdy naszej metodzie nie uda siÄ™ zwrócić obiektu z powodu jakiegoÅ› błędu.

Do góry

PHP 5 vs. PHP 4

Na koniec pozostawiÅ‚em sobie omówienie różnic miÄ™dzy programowaniem obiektowym w PHP 4 i 5. Nie bÄ™dÄ™ wymieniać, czego nie byÅ‚o, bowiem o wiele krótsza lista bÄ™dzie, gdy powiem, co byÅ‚o. A wiÄ™c:

I to w zasadzie wszystko! Poprzednie wersje PHP traktowaÅ‚y programowanie obiektowe po macoszemu, stÄ…d też tak mizerna liczba możliwoÅ›ci. OczywiÅ›cie dla jako takiej kompatybilnoÅ›ci zachowano część zmienionych elementów. Tak wiÄ™c nadal można używać sÅ‚owa var jako substytutu public (ale tylko przy polach), metody zaczynajÄ…ce siÄ™ od razu od function sÄ… domyÅ›lnie publiczne, a w przypadku nieznalezienia konstruktora __construct(), szukany jest ten o nazwie takiej samej, jak klasa.

GÅ‚ówne niekompatybilnoÅ›ci wynikajÄ… ze zmiany zachowania siÄ™ zmiennych obiektowych.

Do góry

Programowanie obiektowe w praktyce

Na koÅ„cu pokrótce powiem, do czego może siÄ™ przydać programowanie obiektowe w codziennej praktyce. Jako programista, bÄ™dziesz zapewne pisać dalej oskryptowanie dla stron WWW. Dotychczas caÅ‚y kod byÅ‚ pisany po prostu ciurkiem i byÅ‚ w nim (krótko mówiÄ…c) burdel. OOP da Ci możliwość napisania silnika, którego klasy i obiekty bÄ™dÄ… zarzÄ…dzaÅ‚y wszystkimi podstawowymi mechanizmami serwisu. Tobie pozostanie już tylko napisać odpowiednie moduÅ‚y, w dodatku w o wiele krótszym czasie - silnik część zadaÅ„ wykona za Ciebie.

Ważne jest, aby pojedyncza klasa opisywaÅ‚a jednÄ… logicznie powiÄ…zanÄ… caÅ‚ość. Zatem powstanie klasa "session" odpowiedzialna za sesjÄ™, "io" do obsÅ‚ugi systemu wejÅ›cia/wyjÅ›cia, klasy jakichÅ› maperów do kontroli danych z formularzy. Tu wkracza na scenÄ™ projektowanie - dobrze jest rozrysować sobie zależność miÄ™dzy poszczególnymi obiektami, sposób dostÄ™pu do nich oraz udostÄ™pniane API. W ten sposób nie stwierdzisz w poÅ‚owie roboty, że wszystko musisz zaczynać od poczÄ…tku. Dobre rozdzielenie zadaÅ„ i planowanie to poÅ‚owa sukcesu.

Jako że w OOP liczy siÄ™ logika i porzÄ…dek, bÄ™dziesz tracić znacznie mniej czasu na zaczÄ™cie poszczególnych projektów. Zamiast pracowicie wycinać co drugÄ… linijkÄ™ (albo i pisać od zera, by siÄ™ nie mÄ™czyć), przeniesiesz do nowego zadania część klas z wczeÅ›niejszych. Po co wyważać otwarte drzwi? Zyskujesz kompletny i (mam nadziejÄ™, że) dobrze zaplanowany interfejs. Wszystko trzeba bÄ™dzie już tylko połączyć.

Powyższe zasady sprawdzajÄ… siÄ™ bardzo dobrze. Sam mam zaprojektowany niewielki (zaledwie 50 kb) silnik stworzony w oparciu o OOP z PHP 5, który realizuje wszystkie zadania zwiÄ…zane z wewnÄ™trznÄ… mechanikÄ… serwisu WWW. Nie jest on jeszcze kompletny, lecz w bieżących projektach używam niektórych jego klas, by zaoszczÄ™dzić sobie czasu i pracy. GdybyÅ› chciaÅ‚ zobaczyć, jak ta poznana wiedza wyglÄ…da i sprawdza siÄ™, przeczytaj inne artykuÅ‚y na Webcity.pl. Polecam również poszukanie informacji o singletonach oraz modelu MVC. Obie te rzeczy zwiÄ…zane sÄ… z programowaniem obiektowym i czasami one lub niektóre ich zaÅ‚ożenia mogÄ… być bardzo przydatne przy projektowaniu wÅ‚asnych rozwiÄ…zaÅ„.

Do góry

Zakończenie

Na tym kończę trzyczęściową serię o programowaniu obiektowym w PHP 5. Mam nadzieję, że wyraźnie wytłumaczyłem wszystkie jego elementy, jak i sam mechanizm działania. Jakby co, moje doświadczenie jest do dyspozycji na naszym forum Webcity.pl. Tam też ciekawscy mogą zadać dodatkowe pytania.

Programowanie obiektowe jest złożoną dziedziną. Opisem jej właściwego wykorzystania oraz projektowania aplikacji z jej użyciem bez problemu można zapełnić kilka opasłych książek i, rzecz oczywista, nie da się tego skompresować w tak niewielkim (stosunkowo) tekście. Miał on pokazać jedynie narzędzia oraz absolutne podstawy - reszta już należy do Ciebie. Samodzielne ćwiczenia i praktyka to podstawy sukcesu.

Autor: Tomasz "Zyx" Jędrzejewski, www.zyxist.com

Do góry

Waszym zdaniem:

Nikt jeszcze nie dodał swojego komentarza. Możesz być pierwszy!


Twoim zdaniem:

Reklama

banner

Partnerzy

CityDesign.pl
phpSolutions