3 wzorce projektowe w ok. 100 liniach kodu PHP

Ostat­nio sporo czytam na temat progra­mo­wa­nia obiek­to­wego. W niemal każdej książce, która wpadła mi w łapki, przy­kłady wzor­ców projek­to­wych podane są w języku Java. Wyjąt­kiem jest Progra­mo­wa­nie obiek­towe w PHP 5 Hasina Haydera.

Do napi­sa­nia postu skło­niły mnie poja­wia­jące się gdzie­nie­gdzie narze­ka­nia progra­mi­stów na PHP, że jakoby ten język to zabawka, że nie da się w nim tworzyć w pełni obiek­to­wego kodu itd. Dzisiaj zade­mon­struję dzia­ła­nie 3 wzor­ców projek­to­wych na raz (Stra­te­gia, Obser­wa­tor, Łańcuch zobowiązań).

Chcemy zrobić coś takiego:

Mamy więc jeden obiekt, który zawiera w sobie dane na temat obiek­tów, które chce powia­da­miać, że coś się u niego zmie­niło. Tak działa typowy wzorzec obser­wa­tor. Ja to jednak zmody­fi­ko­wa­łem tak, że pierw­szy obser­wa­tor w łańcu­chu otrzy­muje infor­ma­cję o zmia­nie stanu obiektu obser­wo­wa­nego. Jeżeli umie sobie pora­dzić z prze­ka­za­nym komu­ni­ka­tem — reaguje. Jeżeli ta infor­ma­cja go zbyt­nio nie inte­re­suje — prze­ka­zuje następ­nemu obiek­towi w łańcu­chu. Tak działa wzorzec łańcuch zobo­wią­zań. Po co mi wzorzec stra­te­gia? Z czystego leni­stwa :-) Po co pisać kilka rodza­jów obser­wa­to­rów do testu, które reagują w inny sposób na komu­ni­katy, skoro można imple­men­to­wać inter­fejs reagu­jący na różne komu­ni­katy. Jeżeli będę chciał testo­wać inny rodzaj komu­ni­ka­tów to napi­szę sobię nową imple­men­ta­cję inter­fejsu reagu­ją­cego i tyle :-)

Bierzemy się więc do roboty. Najpierw tworzymy sobie 2 inter­fejsy. 1 dla wzorca obser­wa­tor, drugi dla łańcu­cha zobowiązań.

interface Obserwujacy {
    public function powiadomienie($komunikat);
}
 
interface Lancuch {
    public function zareaguj($komunikat);
}

Następ­nie tworzymy sobie jakiś obiekt obserwowany:

class Obserwowany {
 
    private $obserwatorzy = array();
    private $komunikat;
 
    public function addObserwujacy(Obserwujacy $o) {
        $this->obserwatorzy[] = $o;
    }
 
    public function setKomunikat($k) {
        $this->komunikat = $k;
        $this->powiadamiaj();
    }
 
    private function powiadamiaj() {
        foreach ($this->obserwatorzy as $obserwator) {
            $obserwator->powiadomienie($this->komunikat);
        }
    }
}

Obiekt ma zale­d­wie 3 metody. Pierw­sza dodaje kolej­nego obser­wa­tora do listy, druga zmie­nia stan obiektu (wysyła komu­ni­kat wszyst­kim), trze­cia dla każdego obser­wa­tora wywo­łuje metodę powia­da­mia­jącą o zmia­nie stanu.

Następ­nie tworzymy klasę Obser­wa­tor, która reaguje na wszyst­kie komunikaty.

class Obserwator implements Obserwujacy , Lancuch {
 
    protected $reagujNa; // implements ReagujNa
    protected $nazwa;
    protected $lancuch;
 
    public function __construct($nazwa, Lancuch $l) {
        $this->reagujNa = new ReagujNaWszystko();
        $this->nazwa = $nazwa;
        $this->lancuch = $l;
    }
 
    public function powiadomienie($komunikat) {
        $this->zareaguj($komunikat);
    }
 
    public function zareaguj($komunikat) {
        if ($this->reagujNa->reaguj($komunikat)) {
            echo '<strong>' . $this->nazwa . '</strong> otrzymal komunikat ' . $komunikat . ' i zareagowal.<br />';
        }
        else {
            $this->lancuch->zareaguj($komunikat);
        }
    }
}

Zmienna $reagujNa imple­men­tuje inter­fejs ReagujNa, który stwo­rzymy poni­żej. Niestety w PHP nie da się wymu­sić okre­ślo­nego typu zmien­nej — w tym lepsza jest Java. Jeżeli ktoś koniecz­nie chce, to może sobie spraw­dzać czy dana zmienna rzeczy­wi­ście imple­men­tuje inter­fejs przed wywołaniem.

interface ReagujNa {
    public function reaguj($komunikat);
}
 
class ReagujNaWszystko implements ReagujNa {
    public function reaguj($komunikat) {
            return true;
    }
}
 
class ReagujNaLiterki implements ReagujNa {
    public function reaguj($komunikat) {
        if (is_string($komunikat)) {
            return true;
        }
        else {
            return false;
        }
    }
}
 
class ReagujNaNumerki implements ReagujNa {
    public function reaguj($komunikat) {
        if (is_numeric($komunikat)) {
            return true;
        }
        else {
            return false;
        }
    }
}

Mamy tutaj trzy klasy posia­da­jące po jednej meto­dzie, które zwra­cają prawdę lub fałsz w zależ­no­ści od poda­nego para­me­tru. Dalej wypa­da­łoby wyko­rzy­stać te klasy zmie­nia­jąc zacho­wa­nie obiektu Obserwator.

class ObserwatorLiterek extends Obserwator {
    public function __construct($nazwa, Lancuch $l) {
        parent::__construct($nazwa,$l);
        $this->reagujNa = new ReagujNaLiterki();
    }
}
 
class ObserwatorNumerkow extends Obserwator {
    public function __construct($nazwa, Lancuch $l) {
        parent::__construct($nazwa,$l);
        $this->reagujNa = new ReagujNaNumerki();
    }
}

Na koniec potrzebne jest nam jesz­cze ostat­nie ogniwo łańcu­cha, nazwane przeze mnie Limi­ter. Cokol­wiek limi­ter dostaje — wyrzuca na wyjściu i tyle.

class Limiter implements Lancuch {
 
    private $nazwa;
 
    public function __construct ($nazwa) {
        $this->nazwa = $nazwa;
    }
    public function zareaguj($komunikat) {
        echo '<strong>Limiter ' . $this->nazwa . '</strong> otrzymal komunikat ' . $komunikat . '<br />';
    }
}

Pora to wszystko prze­te­sto­wać. Tworzymy obiekt obserwowany,

$obserwowany = new Obserwowany();

a następ­nie 4 ciągi obser­wa­to­rów. Tworzymy go od końca. Coś jak Matrioszki.

// ciag 1
$limiter1 = new Limiter('ciagu 1');
$obserwatorWszystkiego = new Obserwator('Obserwator Wszystkiego ciagu 1',$limiter1);
$obserwowany->addObserwujacy($obserwatorWszystkiego);
// ciag 2
$limiter2 = new Limiter('ciagu 2');
$obserwatorLiterek = new ObserwatorLiterek('Obserwator Literek ciagu 2',$limiter2);
$obserwowany->addObserwujacy($obserwatorLiterek);
// ciag 3
$limiter3 = new Limiter('ciagu 3');
$obserwatorNumerkow = new ObserwatorNumerkow('Obserwator Numerkow ciagu 3',$limiter3);
$obserwowany->addObserwujacy($obserwatorNumerkow);
// ciag 4
$limiter4 = new Limiter('ciagu 4');
$obsAll = new Obserwator('Obserwator Wszystkiego ciagu 4',$limiter4);
$obsNum = new ObserwatorNumerkow('Obserwator Numerkow ciagu 4',$obsAll);
$obsLit = new ObserwatorLiterek('Obserwator Literek ciagu 4',$obsNum);
$obserwowany->addObserwujacy($obsLit);

Ostat­nim krokiem jest spraw­dze­nie jak to teraz działa.

$obserwowany->setKomunikat('abc');
echo '----------------------------------';
$obserwowany->setKomunikat(123);

Wynik:
Obserwator Wszystkiego ciagu 1 otrzymal komunikat abc i zareagowal.
Obserwator Literek ciagu 2 otrzymal komunikat abc i zareagowal.
Limiter ciagu 3 otrzymal komunikat abc
Obserwator Literek ciagu 4 otrzymal komunikat abc i zareagowal.
----------------------------------
Obserwator Wszystkiego ciagu 1 otrzymal komunikat 123 i zareagowal.
Limiter ciagu 2 otrzymal komunikat 123
Obserwator Numerkow ciagu 3 otrzymal komunikat 123 i zareagowal.
Obserwator Numerkow ciagu 4 otrzymal komunikat 123 i zareagowal.

A teraz wyja­śnie­nie jak to wszystko działa:

Ciąg 1 zawiera w sobie tylko obser­wa­tora wszyst­kiego i limi­ter. Obser­wa­tor każdy komu­ni­kat trak­tuje jako infor­ma­cję dla siebie i nie prze­ka­zuje dalej do limi­tera (przez co komu­ni­kat nigdy do niego nie dojdzie).

Ciąg 2 ma tylko obser­wa­tora lite­rek i limi­ter. Na pierw­szy, słowny komu­ni­kat zare­ago­wał. Na komu­ni­kat zawie­ra­jący liczbę nie zare­ago­wał i prze­ka­zał dalej do limitera.

Ciąg 3 ma w zamian obser­wa­tora numer­ków i limi­ter. Zacho­wał się dokład­nie odwrot­nie od poprzedniego.

Ciąg 4 ma kolejno obser­wa­to­rów lite­rek, numer­ków, wszyst­kiego i limi­ter. Teraz dokład­nie widać jak to działa. Jeżeli dany obser­wa­tor nie znalazł infor­ma­cji przy­dat­nej dla siebie — po prostu prze­ka­zał następ­nemu obser­wa­to­rowi w ciągu. Warto zauwa­żyć, że obser­wa­tor wszyst­kiego i limi­ter w tym przy­padku nigdy nie zare­aguje, gdyż komu­ni­kat zosta­nie rozwią­zany zanim do niego dojdzie.

Dla chęt­nych zamiesz­czam plik wzorce.php (skaso­wali, a kopii nie mam :-( ). Zachę­cam do ekspe­ry­men­tów i ewen­tu­al­nej prze­bu­dowy obser­wa­to­rów tak, aby reago­wały na konkretne słowa lub liczby.

Podobne wpisy:

  1. Weź mi zrób proce­sor tekstu cz. 2 / 2
  2. Weź mi zrób proce­sor tekstu cz. 1 / 2
  3. Atak klonów w PHP
  4. Wzorzec projek­towy memento w PHP

One comment

  • 42424242
    11 listopada 2012 - 12:39 | Permalink

    jak to dziala to? br> heh

  • Dodaj komentarz

    Twój adres e-mail nie zostanie opublikowany.

    Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <p> <pre lang="" line="" escaped=""> <q cite=""> <strike> <strong>