Wzorzec observer

<?php

interface Subscriber
{
    public function send(): void;
}

class Client implements Subscriber
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function send(): void
    {
        echo 'i got it: ' . $this->name . '<br>';
    }
}

class Subject
{
    private $subscribers = [];

    public function subscribe(Subscriber $subscriber): void
    {
        $this->subscribers[] = $subscriber;
    }

    public function startWork(): void
    {
        sleep(1);
        //code

        foreach ($this->subscribers as $subscriber) {
            $subscriber->send();
        }
    }
}

$client1 = new Client('first client');
$client2 = new Client('second client');
$client3 = new Client('third client');

$subject = new Subject();

$subject->subscribe($client1);
$subject->subscribe($client2);
$subject->subscribe($client3);

$subject->startWork();

2)Implementacja Sedno wzorca Observer (obserwator) polega na rozdzieleniu elementów użytkujących (obserwatorów) od klasy centralnej (podmiotu obserwacji). Obserwatory muszą być informowane o zdarzeniach zachodzących w podmiocie obserwacji. Równocześnie nie chcemy wprowadzać trwałych i sztywnych zależności pomiędzy podmiotem obserwacji a klasami obserwatorów. Możemy więc umożliwić obserwatorom rejestrowanie się w klasie podmiotu. W tym celu powinniśmy uzupełnić klasę Login o trzy nowe metody: rejestracji (attach()), rezygnacji (detach()) i powiadomienia (notify()), przystosowując klasę do wymogów wyróżniających podmioty obserwacji interfejsu (tutaj ma on nazwę Observable):

<?php
interface Observable
{
    function attach(Observer $observer);
    function detach(Observer $observer);
    function notify();
}

<?php
// Klasa Login…
class Login implements Observable
{
    private $observers = array();
    private $storage;
    const LOGIN_USER_UNKNOWN = 1;
    const LOGIN_WRONG_PASS = 2;
    const LOGIN_ACCESS = 3;
    function attach(Observer $observer)
    {
        $this->observers[] = $observer;
    }
    function detach(Observer $observer)
    {
        $this->observers = array_filter($this->observers, function ($a) use ($observer)
        {
            return (!($a === $observer));
        });
    }
    function notify()
    {
        foreach ($this->observers as $obs)
        {
            $obs->update($this);
        }
    }
    
    //
    
}

Mamy więc klasę podmiotu utrzymującą listę obiektów obserwatorów. Obiekty te są dodawane do listy z zewnątrz poprzez wywołanie metody attach(). Rezygnacja z obserwacji i usunięcie z listy następuje w wyniku wywołania metody detach(). Z kolei wywołanie metody notify() służy jako powiadomienie obiektów obserwatorów o potencjalnie interesujących ich zdarzeniach. Implementacja tej metody sprowadza się do przejrzenia tablicy obiektów obserwatorów i wywołania na rzecz każdego z nich metody update(). Wywołanie metody rozsyłającej powiadomienia następuje we wnętrzu klasy Login, w ciele metody handleLogin():

//
function handleLogin($user, $pass, $ip)
{
    switch (rand(1, 3))
    {
        case 1:
            $this->setStatus(self::LOGIN_ACCESS, $user, $ip);
            $isvalid = true;
        break;
        case 2:
            $this->setStatus(self::LOGIN_WRONG_PASS, $user, $ip);
            $isvalid = false;
        break;
        case 3:
            $this->setStatus(self::LOGIN_USER_UNKNOWN, $user, $ip);
            $isvalid = false;
        break;
    }
    $this->notify();
    return $isvalid;
}

Zdefiniujmy interfejs klas obserwatorów:

interface Observer {
 function update(Observable $observable);
}

Do listy obserwatorów można dodawać (za pośrednictwem metody attach() klasy podmiotu obserwacji) dowolne obiekty, które implementują interfejs Observable. Tak wygląda tworzenie konkretnego egzemplarza:

<?php
class SecurityMonitor implements Observer
{
    function update(Observable $observable)
    {
        $status = $observable->getStatus();
        if ($status[0] == Login::LOGIN_WRONG_PASS)
        {
            // wyślij wiadomość do administratora…
            print __CLASS__ . "\twysyłam wiadomość do administratora\n";
        }
    }
}
$login = new Login();
$login->attach(new SecurityMonitor());

Zwróćmy uwagę, jak obiekt obserwatora odwołuje się do egzemplarza klasy Observable (podmiotu obserwacji) w celu pozyskania dodatkowych informacji o zdarzeniu. Metody, za pośrednictwem których obiekty obserwatorów mogłyby dowiadywać się o stanie, powinny zostać udostępnione właśnie w klasie podmiotu obserwacji. W tym przypadku klasa podmiotu ma zdefiniowaną metodę getStatus(), dzięki której obiekty obserwatorów mogą dowiadywać się o bieżącym stanie obiektu obserwowanego. Pojawia się tutaj pewien problem. Otóż w wywołaniu metody Login::getStatus() klasa SecurityMonitor bazuje na wiedzy o klasie Login, na której nie powinna polegać. Przecież w wywołaniu otrzymuje obiekt Observable, ale nie ma żadnej gwarancji, że będzie to właśnie obiekt Login. Mamy tu kilka możliwości: możemy rozszerzyć interfejs Observable tak, aby zawierał w sobie deklarację metody getStatus(), i możemy od razu przemianować interfejs na ObservableLogin, sygnalizując, że ma związek z klasami Login. Możemy też utrzymać ogólny interfejs Observable i obarczyć klasy Observable odpowiedzialnością za to, aby podmioty obserwacji były odpowiedniego typu. Możemy wtedy złożyć na nie również zadanie kojarzenia się z podmiotami obserwacji. Ponieważ będziemy mieć więcej niż jeden typ Observer, a zamierzamy zaimplementować przy okazji czynności porządkowe wspólne dla wszystkich podtypów, możemy od razu udostępnić abstrakcyjną klasę bazową:

<?php
abstract class LoginObserver implements Observer
{
    private $login;
    function __construct(Login $login)
    {
        $this->login = $login;
        $login->attach($this);
    }
    function update(Observable $observable)
    {
        if ($observable === $this->login)
        {
            $this->doUpdate($observable);
        }
    }
    abstract function doUpdate(Login $login);
}

Klasa LoginObserver wymaga do konstrukcji obiektu typu Login. W konstruktorze zachowuje sobie referencję obiektu i wywołuje własną metodę Login::attach(). W wywołaniu update() następuje sprawdzenie, czy przekazany obiekt Observable jest w istocie referencją obserwowanego podmiotu, po czym dochodzi do wywołania metody szablonowej doUpdate(). Teraz możemy utworzyć cały zestaw obiektów LoginObserver, z których każdy będzie operował na obiekcie Login, a nie na dowolnym obiekcie implementującym nasz stary interfejs Observable:

<?php
class SecurityMonitor extends LoginObserver
{
    function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        if ($status[0] == Login::LOGIN_WRONG_PASS)
        {
            // wysłanie wiadomości do administratora
            print __CLASS__ . ":\twysyłam wiadomość do administratora\n";
        }
    }
}

class GeneralLogger extends LoginObserver
{
    function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        // dodanie danych do rejestru
        print __CLASS__ . ":\tdodaję dane logowania do rejestru\n";
    }
}
class PartnershipTool extends LoginObserver
{
    function doUpdate(Login $login)
    {
        $status = $login->getStatus();
        // sprawdzenie adresu IP
        // ustawienie ciasteczka dla dopuszczonego IP
        print __CLASS__ . ":\tustawiam ciasteczko dla dopuszczonego IP\n";
    }
}
//Tworzenie i podłączanie obserwatorów LoginObserver jest teraz wykonywane w czasie konkretyzacji obiektów:
$login = new Login();
new SecurityMonitor($login);
new GeneralLogger($login);
new PartnershipTool($login);

 

Komentarze wyłączone