<?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);