W jaki sposób splsubject / SplObserver jest przydatny?

Standardowa biblioteka PHP Zawiera to, co niektóre zasoby nazywają referencyjną implementacją wzorca Observer, poprzez SplSubject oraz SplObserver klasy. Na całe życie nie mogę zrozumieć, jak są one bardzo przydatne, bez sposobu przekazywania rzeczywistych wydarzeń lub innych informacji wraz z powiadomieniami: {]}

class MySubject implements SplSubject {
    protected $_observers = [];

    public function attach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        $this->_observers[$id] = $observer;
    }

    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);

        if (isset($this->_observers[$id])) {
            unset($this->_observers[$id]);
        }
    }

    public function notify() {
        foreach ($this->_observers as $observer) {
            $observer->update($this);
        }
    }
}

class MyObserver implements SplObserver {
    public function update(SplSubject $subject) {
        // something happened with $subject, but what
        // was it???
    }
}

$subject = new MySubject();
$observer = new MyObserver();

$subject->attach($observer);
$subject->notify();

Wydaje się, że te interfejsy są prawie bezużyteczne dla każdego prawdziwego problemu. Czy ktoś może oświecić ja?


Edit:

Oto mój największy problem z interfejsem (chociaż są inne):

public function update(SplSubject $subject, Event $event) { /* ... */ }

...nets następujący błąd krytyczny:

PHP Fatal error:  Declaration of MyObserver::update() must be compatible with SplObserver::update(SplSubject $SplSubject)

Edytuj #2:

Wprowadzenie dodatkowych parametrów opcjonalnych poprzez podanie ich wartości domyślnych zapobiega krytycznemu błędowi i zapewnia sposób na przekazanie kontekstu, co sprawia, że implementacje są opłacalne. Nie byłem wcześniej tego świadomy, więc to w zasadzie odpowiada na moje pytanie. Rozwiązaniem jest przejście własne dane zdarzenia/wiadomości i sprawdź ich istnienie w SplObserver::update().

Author: FtDRbwLXw6, 2012-12-08

6 answers

Możesz zaimplementować metodę update z opcjonalnym parametrem i nadal spełniać interfejs SplSubject.

class MyObserver implements SplObserver {
    public function update(SplSubject $subject, $eventData = null) {
        if (is_null($eventData))
            // carefull
    }
}
 6
Author: goat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-12 05:25:27

It seems like these interfaces are pretty much useless for any real world problem. Can someone enlighten me?

Interfejs

Podczas gdy klasy abstrakcyjne pozwalają zapewnić pewną miarę implementacji, interfejsy są czystymi szablonami. An interface może tylko define functionality; nigdy nie może tego zaimplementować. Interfejs jest deklarowany za pomocą słowa kluczowego interface. Może zawierać właściwości i deklaracje metod, ale nie ciała metod.

Przypadek użycia interfejsu

Na przykład, jeśli chcesz, aby twój projekt obsługiwał różne bazy danych . abyś mógł Zmień swoją bazę danych w przyszłości lepiej jest użyć interfejsów zawierających procedury właściwości w pliku klasy z out zmieniającymi obiektami

By itself, interfaces are not very useful ponieważ nie można tworzyć instancji interfejsów, ale Interfejsy odgrywają kluczową rolę w egzekwowaniu metod projektowania zorientowanego obiektowo which in real sense makes your live easier as a programmer, ponieważ główną zachętą dla programowania zorientowanego obiektowo jest enkapsulacja (nie obchodzi cię, jak zaimplementowana jest zdolność. Ty, jako programista, jesteś narażony tylko na interfejs. To jest również dobrym sposobem na obserwowanie architektury systemu)

SplSubject & SplObserver

Ortogonalność jest cnotą , jednym z celów programistów powinno być budowanie komponentów, które mogą być zmieniane lub przenoszone z minimalnym wpływem na inne komponenty.

Jeśli każda zmiana w jednym komponencie wymaga falowania zmian w innym miejscu kodu, zadanie rozwoju może szybko stać się spiralą tworzenia błędów i eliminacja.

Nie ma specjalnej funkcji SplSubject i SplObserver ponieważ oba są interface to implement the Observer Design Pattern.

Obserwator wzór

Wzorzec obserwatora to programowy wzorzec projektowy, w którym obiekt, zwany podmiotem, utrzymuje listę swoich zależności, zwanych obserwatorami i powiadamia ich automatycznie o wszelkich zmianach stanu, zwykle przez wywołanie jednej z ich metod. Służy głównie do implementacji rozproszonej obsługi zdarzeń systemy

  • wzorzec obserwatora definiuje zależność jeden do wielu pomiędzy obiektem podmiotu A dowolną liczbą obiektów obserwatora, tak że gdy obiekt obiektu zmienia stan, wszystkie jego obiekty obserwatora są automatycznie powiadamiane i aktualizowane.
  • wzorzec obserwatora zasadniczo pozwala nieograniczonej liczbie obiektów obserwować lub słuchać zdarzeń w obserwowanym obiekcie (lub podmiocie), rejestrując się. Po zarejestrowaniu obserwatorów na wydarzenie, podmiot będzie / align = "left" /
  • podmiot zajmuje się tym, przechowując kolekcję obserwatorów i powtarzając ją, gdy nastąpi zdarzenie, aby powiadomić każdego obserwatora.
  • wzór obserwatora rejestruje obserwatorów z obiektem.
  • Możesz mieć wielu obserwatorów. Tester musi prowadzić listę zarejestrowanych obserwatorów, a po wystąpieniu zdarzenia wystrzeli (dostarczy powiadomienie) wszystkich zarejestrowanych obserwatorów.
  • Wyrejestrowanie możliwe również wtedy, gdy nie potrzebujemy żadnych obserwator.

Przykład 1. System powiadamiania o stopach procentowych dla kredytu

$loan = new Loan("Mortage", "Citi Bank", 20.5);
$loan->attach(new Online());
$loan->attach(new SMS());
$loan->attach(new Email());

echo "<pre>";
$loan->setIntrest(17.5);

Wyjście

Online    : Post online about modified Intrest rate of : 17.50
Send SMS  : Send SMS to premium subscribers : 17.50
Send Email: Notify mailing list : 17.50

Przykład 2. Simple User Register Monitor

$users = new Users();

new Audit($users);
new Logger($users);
new Security($users);

$users->addUser("John");
$users->addUser("Smith");
$users->addUser("Admin");

Wyjście

Audit    : Notify Audit about John
Log      : User John Create at Wed, 12 Dec 12 12:36:46 +0100
Audit    : Notify Audit about Smith
Log      : User Smith Create at Wed, 12 Dec 12 12:36:46 +0100
Audit    : Notify Audit about Admin
Log      : User Admin Create at Wed, 12 Dec 12 12:36:46 +0100
Security : Alert trying to create Admin

Zaleta wzorca projektowego obserwatora: Główną zaletą jest luźne sprzężenie między obiektami nazywanymi obserwatorami i obserwowalnymi. Tester zna tylko listę obserwatorów, nie obchodzi go, jak mają swoją implementację.Wszyscy obserwatorzy są zgłaszany przez testera w jednym wywołaniu zdarzenia jako komunikat Broadcast

Wady wzoru konstrukcyjnego obserwatora:

  • wadą jest to, że czasami, Jeśli pojawi się jakikolwiek problem, debugowanie staje się bardzo trudne, ponieważ przepływ kontroli jest niejawnie między obserwatorami i obserwowalny możemy przewidzieć, że teraz obserwator będzie strzelać i jeśli istnieje łańcuch między obserwatorami, to debugowanie staje się bardziej skomplikowane.
  • Kolejną kwestią jest zarządzanie pamięcią, gdy radzenie sobie z dużymi obserwatorami

Klasy Wspólne

abstract class Observable implements SplSubject {
    protected $_observers = [];

    public function attach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        $this->_observers[$id] = $observer;
    }

    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);

        if (isset($this->_observers[$id])) {
            unset($this->_observers[$id]);
        }
    }

    public function notify() {
        foreach ( $this->_observers as $observer ) {
            $observer->update($this);
        }
    }
}



abstract class Observer implements SplObserver {
    private $observer;

    function __construct(SplSubject $observer) {
        $this->observer = $observer;
        $this->observer->attach($this);
    }
}

Wczytaj Przykładowe Klasy

class Loan extends Observable {
    private $bank;
    private $intrest;
    private $name;

    function __construct($name, $bank, $intrest) {
        $this->name = $name;
        $this->bank = $bank;
        $this->intrest = $intrest;
    }

    function setIntrest($intrest) {
        $this->intrest = $intrest;
        $this->notify();
    }

    function getIntrest() {
        return $this->intrest;
    }
}

class Online implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Online    : Post online about modified Intrest rate of : %0.2f\n",$loan->getIntrest());
    }
}

class SMS implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Send SMS  : Send SMS to premium subscribers : %0.2f\n",$loan->getIntrest());
    }
}

class Email implements SplObserver {

    public function update(SplSubject $loan) {
        printf("Send Email: Notify mailing list : %0.2f\n",$loan->getIntrest());
    }
}

Użytkownik Rejestruje Przykładowe Klasy

class Users extends Observable {
    private $name;

    function addUser($name) {
        $this->name = $name;
        $this->notify();
    }

    function getName() {
        return $this->name;
    }
}
class Audit extends Observer {

    public function update(SplSubject $subject) {
        printf("Audit    : Notify Autify about %s\n", $subject->getName());
    }
}
class Logger extends Observer {

    public function update(SplSubject $subject) {
        printf("Log      : User %s Create at %s\n", $subject->getName(),date(DATE_RFC822));
    }
}
class Security extends Observer {
    public function update(SplSubject $subject) {
        if($subject->getName() == "Admin")
        {
            printf("Security : Alert trying to create Admin\n");
        }
    }
}
 35
Author: Baba,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-14 17:43:28

To dość proste: wzorzec podmiot / obserwator nie jest przydatny dla systemu zdarzeń.

Wzór obserwatora nie nadaje się do powiedzenia, "to coś zostało zaktualizowane przez X". Zamiast tego po prostu mówi, że został zaktualizowany. Stworzyłem elastyczną klasę mediatora , która może być wykorzystana w systemie zdarzeń. W zależności od potrzeb pomocne może być bardziej sztywne API, ale można go wykorzystać jako inspirację.

Więc kiedy jest wzorzec podmiotu / obserwatora przydatne?

Jest to dość powszechny wzorzec podczas aktualizacji GUI, ponieważ niektóre obiekty uległy zmianie. Tak naprawdę nie musi wiedzieć, co go zmieniło i dlaczego, po prostu musi zaktualizować. Natura HTTP tak naprawdę nie nadaje się do tego konkretnego wzorca, ponieważ twój kod PHP nie jest powiązany bezpośrednio z HTML. Musisz złożyć nową prośbę, aby ją zaktualizować.

Krótko mówiąc, wzorzec Subject / Observer nie jest tak naprawdę przydatny w PHP. Ponadto interfejs nie jest tak przydatny, ponieważ użyj instanceof, aby uzyskać właściwy typ obiektu. Po prostu napisałbym swój własny interfejs, a nie sobie z tym poradzić.

 13
Author: Levi Morrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-19 13:56:21

Te dwa interfejsy nie mają dołączonej magicznej funkcjonalności, więc ich implementacja nic nie robi. Są używane tylko w celach informacyjnych. Istnieją inne wewnętrzne interfejsy PHP, takie jak SeekableIterator. Nie ma żadnej magicznej funkcjonalności dołączonej do metody seek i musisz ją zaimplementować samodzielnie.

Istnieją pewne wewnętrzne interfejsy PHP i takie jak Traversable, które mają specjalną funkcjonalność, ale nie jest to przypadek SplSubject i SplObserver -- to zasadniczo tylko sugerowany interfejs do implementacji wzorca obserwatora.

Jeśli chodzi o co się stało , ta informacja nie jest częścią interfejsu, ponieważ nie jest abstrakcyjna. To do ciebie należy wdrożenie.

interface Event extends SplSubject {
   public function getEventData();
}

class MyEvent implements Event {
   //MySubject implementation above
   public function getEventData() {
      return "this kind of event happened";
   }
}

Możesz również całkowicie zignorować interfejs Event lub po prostu użyć instanceof checks (Brzydula), aby zobaczyć, jaki "obiekt" jest przekazywany do metody.

Jeśli chodzi o przykład z prawdziwego świata, ten link zapewnia jeden, chociaż użycie SplObserver/SplSubject nie są bezwzględnie konieczne, w końcu są tylko interfejsami. Zasadniczo możesz mieć ExceptionHandler klasę przedmiotu i kilku obserwatorów, na przykład Mailer. Możesz użyć set_exception_handler(array($handler, 'notify')); i każdy wyjątek, który zostanie wyrzucony, powiadamia wszystkich obserwatorów (np. Mailer, który wysyła wiadomość e-mail o wyjątku, który został złapany - będziesz musiał uzyskać wyjątek od innej metody / członka ExceptionHandler).

EDIT: widzę z komentarzy, że planujesz użyć innego argumentu do update aby przekazać zdarzenie jako oddzielny obiekt. Myślę, że to jest w porządku, ale moja sugestia jest po prostu, aby nie oddzielić temat i pojęcia zdarzenia i dać podmiotowi możliwość albo zawierać dane zdarzenia lub być same dane zdarzenia. Trzeba by sprawdzić, czy otrzymany obiekt event nie jest null.

 7
Author: Explosion Pills,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-12 14:36:35

Jak każdy interfejs jest bezużyteczny, dopóki go nie zaimplementujesz. Implementując je możesz mieć aplikację sterowaną zdarzeniami

Wyobraź sobie, że masz zdarzenie "applicationStart", na którym musisz uruchomić 10 funkcji.

function applicationStart() {
   // Some other logic 
   fnCall1();
   fnCall2();
   fnCall3();
   fnCall4();
   fnCall5();
   fnCall6();
   fnCall7();
   fnCall8();
   fnCall9();
   fnCall10();
   // Some other logic 
}

Teraz wyobraź sobie, że musisz przetestować tę funkcję, która wywoła zależność od wszystkich pozostałych 10 funkcji.

Jeśli używasz SplSubject/SplObserver:

function applicationStart() {
    // Logic
    $Subject->notify();
    // Logic
}

Teraz, gdy go testujesz, musisz tylko upewnić się, że wyzwalasz Zdarzenie. Bez wykonania innych funkcje.

Plus kod wygląda czystsze, ponieważ nie polute go z logiki biznesowej, która nie należy tam. I jedno ładne, łatwe miejsce do dodania wyzwalaczy

 1
Author: E_p,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-12-12 02:01:56

Spójrz na https://github.com/thephpleague/event wykonuje swoją pracę bardzo dobrze. Myślę, że jest to najlepszy pakiet dzisiaj do tego celu. Nie widzę też żadnej wartości w

public function notify(/* without args */) {

Z league / event będziesz miał następujące. Na przykład mam listę e-mail i chcę obsługiwać zdarzenia, gdy nowy e-mail zostanie dodany do listy.

class EmailList
{
    const EVENT_ADD_SUBSCRIBER = 'email_list.add_subscriber';
    public function __construct($name, $subscribers = [])
    {
        // do your stuff 
        $this->emitter = new Emitter(); 
    }


    /**
     * Adds event listeners to this list
     * @param $event
     * @param $listener
     */
     public function addListener($event, $listener)
     {
         $this->emitter->addListener($event, $listener);
     } 

    /**
     * Adds subscriber to the list
     * @param Subscriber $subscriber
     */
    public function addSubscriber(Subscriber $subscriber)
    {
        // do your stuff 
        $this->emitter->emit(static::EVENT_ADD_SUBSCRIBER, $subscriber);
    }
}

// then in your code
$emailList = new EmailList();
$emailList->addListener(EmailList::EVENT_ADD_SUBSCRIBER, function($eventName, $subscriber) {
});
 0
Author: radzserg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-17 15:38:11