3 wzorce projektowe w ok. 100 liniach kodu PHP

Ostat­nio sporo czytam na temat programowania obiek­towego. W nie­mal każ­dej książce, która wpadła mi w łapki, przy­kłady wzor­ców projek­towych podane są w języku Java. Wyjąt­kiem jest Programowanie obiek­towe w PHP 5 Hasina Haydera.

Do napisania postu skłoniły mnie pojawiające się gdzie­nie­gdzie narzekania programistów na PHP, że jakoby ten język to zabawka, że nie da się w nim tworzyć w pełni obiek­towego kodu itd. Dzisiaj zademon­struję działanie 3 wzor­ców projek­towych na raz (Strategia, Obser­wator, Ł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 powiadamiać, że coś się u niego zmieniło. Tak działa typowy wzorzec obser­wator. Ja to jed­nak zmodyfikowałem tak, że pierw­szy obser­wator w łańcuchu otrzymuje infor­mację o zmianie stanu obiektu obser­wowanego. Jeżeli umie sobie poradzić z prze­kazanym komunikatem — reaguje. Jeżeli ta infor­macja go zbyt­nio nie interesuje — prze­kazuje następ­nemu obiek­towi w łańcuchu. Tak działa wzorzec łańcuch zobowiązań. Po co mi wzorzec strategia? Z czystego lenistwa :-) Po co pisać kilka rodzajów obser­watorów do testu, które reagują w inny spo­sób na komunikaty, skoro można implemen­tować inter­fejs reagujący na różne komunikaty. Jeżeli będę chciał testować inny rodzaj komunikatów to napiszę sobię nową implemen­tację inter­fejsu reagującego i tyle :-)

Bierzemy się więc do roboty. Naj­pierw tworzymy sobie 2 inter­fejsy. 1 dla wzorca obser­wator, drugi dla łańcucha 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 zaled­wie 3 metody. Pierw­sza dodaje kolej­nego obser­watora do listy, druga zmienia stan obiektu (wysyła komunikat wszyst­kim), trzecia dla każ­dego obser­watora wywołuje metodę powiadamiającą o zmianie stanu.

Następ­nie tworzymy klasę Obser­wator, 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 implemen­tuje inter­fejs ReagujNa, który stworzymy poniżej. Nie­stety w PHP nie da się wymusić określonego typu zmien­nej — w tym lep­sza jest Java. Jeżeli ktoś koniecz­nie chce, to może sobie spraw­dzać czy dana zmienna rzeczywi­ście implemen­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 posiadające po jed­nej metodzie, które zwracają prawdę lub fałsz w zależ­no­ści od podanego parametru. Dalej wypadałoby wykorzystać te klasy zmieniając zachowanie 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ńcucha, nazwane przeze mnie Limiter. Cokol­wiek limiter 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­testować. Tworzymy obiekt obserwowany,

$obserwowany = new Obserwowany();

a następ­nie 4 ciągi obser­watoró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­dzenie 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śnienie jak to wszystko działa:

Ciąg 1 zawiera w sobie tylko obser­watora wszyst­kiego i limiter. Obser­wator każdy komunikat trak­tuje jako infor­mację dla siebie i nie prze­kazuje dalej do limitera (przez co komunikat nigdy do niego nie dojdzie).

Ciąg 2 ma tylko obser­watora literek i limiter. Na pierw­szy, słowny komunikat zareagował. Na komunikat zawierający liczbę nie zareagował i prze­kazał dalej do limitera.

Ciąg 3 ma w zamian obser­watora numer­ków i limiter. Zachował się dokład­nie odwrot­nie od poprzedniego.

Ciąg 4 ma kolejno obser­watorów literek, numer­ków, wszyst­kiego i limiter. Teraz dokład­nie widać jak to działa. Jeżeli dany obser­wator nie znalazł infor­macji przy­dat­nej dla siebie — po prostu prze­kazał następ­nemu obser­watorowi w ciągu. Warto zauważyć, że obser­wator wszyst­kiego i limiter w tym przy­padku nigdy nie zareaguje, gdyż komunikat zostanie roz­wiązany zanim do niego dojdzie.

Dla chęt­nych zamiesz­czam plik wzorce.php. Zachęcam do eks­perymen­tów i ewen­tual­nej prze­budowy obser­watorów tak, aby reagowały na kon­kretne słowa lub liczby.

Printed from: http://spiechu.pl/2009/07/05/3-wzorce-projektowe-w-ok-100-liniach-kodu-php/ .
© 2010.

Leave a Reply