php observer-pattern spl

php - ¿Por qué es útil SplSubject/SplObserver?



observer-pattern (6)

Es bastante simple: el patrón sujeto / observador no es útil para un sistema de eventos.

El patrón de observador no se presta a decir "Esta cosa fue actualizada por X". En su lugar, simplemente dice que fue actualizado. De hecho, he creado una clase de mediador flexible que podría aprovecharse para un sistema de eventos. Dependiendo de sus necesidades, una API más rígida puede ser útil, pero puede usar eso como inspiración.

Entonces, ¿cuándo es útil el patrón sujeto / observador?

Es un patrón bastante común al actualizar una GUI porque se modificó algún objeto. Realmente no necesita saber qué lo cambió o por qué, solo que necesita actualizarlo. La naturaleza de HTTP realmente no se presta a este patrón en particular porque su código PHP no está vinculado directamente al HTML. Tienes que hacer una nueva solicitud para actualizarlo.

En resumen, el patrón de sujeto / observador no es realmente tan útil en PHP. Además, la interfaz no es tan útil porque tiene que usar instanceof para obtener el tipo de sujeto adecuado. Yo solo escribiría mi propia interfaz y no lidiaría con eso.

La biblioteca estándar de PHP incluye lo que algunos recursos llaman una implementación de referencia del patrón Observer, a través de las clases SplSubject y SplObserver . Por mi vida, no puedo entender cómo estos son muy útiles sin la forma de pasar eventos reales o cualquier otra información junto con las notificaciones:

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

Parece que estas interfaces son bastante inútiles para cualquier problema del mundo real. ¿Alguien me puede iluminar?

Editar:

Aquí está mi mayor problema con la interfaz (aunque hay otros):

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

... redes el siguiente error fatal:

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

Edición # 2:

Hacer que los parámetros adicionales sean opcionales al darles valores predeterminados previene el error fatal y proporciona una manera de pasar el contexto, haciendo que las implementaciones valgan la pena. Anteriormente no era consciente de esto, así que esto responde bastante a mi pregunta. La solución es pasar sus propios datos de eventos / mensajes, y verificar su existencia dentro de SplObserver::update() .


Como cualquier interfaz es inútil hasta que la implementes. Al implementar aquellos puedes tener una aplicación dirigida por eventos.

Imagina que tienes un evento "applicationStart" que necesitas para ejecutar 10 funciones en él.

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

Ahora imagina que necesitas probar esta función, activarías la dependencia de las otras 10 funciones.

Si usa SplSubject / SplObserver:

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

Ahora, cuando lo prueba, solo necesita asegurarse de que desencadena un evento. Sin ejecución de otras funciones.

El código Plus se ve más limpio, ya que no lo contamina con la lógica empresarial que no pertenece a él. Y un buen lugar fácil para agregar disparadores


Echa un vistazo a https://github.com/thephpleague/event que hace el trabajo muy bien. Creo que es el mejor paquete hoy para este propósito. Tampoco veo ningún valor en

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

Con la liga / evento tendrás seguidores. Por ejemplo, tengo una lista de correo electrónico y quiero manejar eventos cuando se agrega un nuevo correo electrónico a la lista.

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) { });


Estas dos interfaces no tienen una funcionalidad mágica adjunta, por lo que su implementación no hace nada. En realidad, sólo se utilizan para fines de referencia. Hay otras interfaces internas de PHP como esta, como SeekableIterator . No hay una funcionalidad mágica adjunta al método de seek , y debes implementarla tú mismo.

Existen algunas interfaces internas de PHP y, por ejemplo, Traversable que obtienen una funcionalidad especial, pero este no es el caso de SplSubject y SplObserver , es esencialmente una interfaz sugerida para una implementación del patrón Observer.

En cuanto a lo que sucedió, esa información no es parte de la interfaz ya que no es abstracta. Depende de usted para implementar.

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

También puede ignorar la interfaz del Event completo o simplemente usar las pruebas de instanceof (fea) para ver qué tipo de "Asunto" se está pasando al método.

En cuanto a un ejemplo del mundo real, este enlace proporciona uno, aunque el uso de SplObserver / SplSubject no es estrictamente necesario; son simplemente interfaces después de todo. Esencialmente, podría tener una clase de sujeto ExceptionHandler y algunos observadores, por ejemplo, Mailer . Puede usar set_exception_handler(array($handler, ''notify'')); y cualquier excepción lanzada notifica a todos los observadores (p. ej., Mailer , que envía un correo electrónico sobre la excepción detectada; tendría que obtener la excepción de algún otro método / miembro de ExceptionHandler ).

EDITAR: veo en los comentarios que planea usar otro argumento para update y pasar el evento como un objeto separado. Supongo que está bien, pero mi sugerencia es simplemente no separar los conceptos Asunto y Evento y otorgarle al Asunto la capacidad de contener datos de eventos o ser los datos del evento en sí. Tendría que comprobar que el objeto de evento que recibe no es nulo.


Puede implementar el método de actualización con un parámetro opcional y aún así satisfacer la interfaz SplSubject.

class MyObserver implements SplObserver { public function update(SplSubject $subject, $eventData = null) { if (is_null($eventData)) // carefull } }


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

Interfaz

Mientras que las clases abstractas le permiten proporcionar alguna medida de implementación, las interfaces son puras plantillas. Una interface solo puede define functionality ; Nunca puede implementarlo. Se declara una interfaz con la palabra clave de la interfaz. Puede contener propiedades y declaraciones de métodos, pero no cuerpos de métodos.

Caso de uso de la interfaz

Por ejemplo, si desea que su proyecto debe soportar diferentes bases de datos. para que pueda cambiar su base de datos en el futuro, es mejor usar interfaces que contengan procedimientos de propiedad en el archivo de clase sin alterar objetos

By itself, interfaces are not very useful porque no puede crear instancias de interfaces, pero las interfaces son instrumentales para reforzar las metodologías de diseño orientado a objetos, lo which in real sense makes your live easier as a programmer porque el principal incentivo para la programación orientada a objetos es la encapsulación . No importa cómo se implementa una capacidad. Usted, como programador, está expuesto solo a la interfaz. Esta también es una buena manera de observar la arquitectura del sistema.

SplSubject & SplObserver

La ortogonalidad es una virtud. Uno de los objetivos que deben tener los programadores es construir componentes que puedan modificarse o moverse con un impacto mínimo en otros componentes.

Si cada cambio que realiza en un componente requiere una serie de cambios en otras partes del código base, la tarea de desarrollo puede convertirse rápidamente en una espiral de creación y eliminación de errores.

No hay una característica especial de SplSubject y SplObserver porque ambos son una interface to implement the Observer Design Pattern.

Patrón observador

El patrón de observador es un patrón de diseño de software en el que un objeto, llamado sujeto, mantiene una lista de sus dependientes, llamados observadores, y les notifica automáticamente cualquier cambio de estado, generalmente llamando a uno de sus métodos. Se utiliza principalmente para implementar sistemas distribuidos de manejo de eventos.

  • El patrón Observador define una dependencia de uno a muchos entre un objeto sujeto y cualquier número de objetos observadores, de modo que cuando el objeto sujeto cambia de estado, todos sus objetos observadores se notifican y actualizan automáticamente.
  • El patrón del observador esencialmente permite que un número ilimitado de objetos observe o escuche eventos en el objeto observado (o sujeto) registrándose ellos mismos. Después de que los observadores se hayan registrado en un evento, el sujeto los notificará cuando se dispare el evento.
  • El sujeto maneja esto almacenando una colección de observadores e iterándola cuando ocurre el evento para notificar a cada observador.
  • Observer Pattern registra a los observadores con un sujeto.
  • Es posible que tenga varios observadores. El sujeto debe mantener una lista de observadores registrados y, cuando ocurre un evento, dispara (proporciona notificación) a todos los observadores registrados.
  • Anular el registro también es posible cuando no necesitamos ningún observador.

Ejemplo 1. Sistema de notificación de tasa de interés para un préstamo

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

Salida

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

Ejemplo 2. Monitor de registro de usuario simple

$users = new Users(); new Audit($users); new Logger($users); new Security($users); $users->addUser("John"); $users->addUser("Smith"); $users->addUser("Admin");

Salida

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

Ventaja del patrón de diseño del observador: la principal ventaja es el acoplamiento suelto entre los objetos llamados observador y observable. El sujeto solo conoce la lista de observadores y no le importa cómo tienen su implementación. Todos los observadores son notificados por sujeto en una sola llamada de evento como comunicación de difusión.

Desventaja del patrón de diseño del observador:

  • La desventaja es que en algún momento si surge algún problema, la depuración se vuelve muy difícil porque el flujo de control es implícitamente entre observadores y observables, podemos predecir que ahora el observador se activará y si hay una cadena entre observadores, la depuración se vuelve más compleja.
  • Otro problema es la gestión de la memoria cuando se trata de grandes observadores.

Clases comunes

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

Cargar clases de ejemplo

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()); } }

Clases de ejemplo de registro de usuario

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"); } } }