Home > Article > Backend Development > Observer Pattern where walls have ears
Everyone must have logged in to the system. After verifying the username and password, the login is successful. The log system should record the login. If there is a login error, the security system should record the error. The email system should also send relevant emails to the administrator. etc. This is like the login system being monitored by many people. Once there is any trouble, other systems will immediately learn about it. Then try using the observer mode. The class diagram is as follows:
Very simple mode, implementation code:
Php code
<?php interface Observable{ function attach( Observer $observer ); function detach( Observer $observer ); function notify(); } class login implements Observable{ const LOGIN_USER_UNKNOW = 1; const LOGIN_WRONG_PASS = 2; const LOGIN_ACCESS = 3; private $status = array(); private $observers = array(); public function setStatus( $status, $user, $ip ) { $this->status = array( $status, $user, $ip ); } public function getStatus() { return $this->status; } public function handleLogin( $user, $pass, $ip ) { switch ( mt_rand( 1, 3 ) ) { case 1: $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip ); $ret = false; break; case 2: $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip ); $ret = false; break; case 3: $this->setStatus( self::LOGIN_ACCESS, $user, $ip ); $ret = true; break; } $this->notify(); return $ret; } public function attach( Observer $observer ) { $this->observers[] = $observer; } public function detach( Observer $observer ) { $newObservers = array(); foreach ( $this->observers as $obs ) { if ( $obs !== $observer ) $newObservers[] = $obs; } $this->observers = $newObservers; } public function notify() { foreach ( $this->observers as $obs ) { $obs->update( $this ); } } } interface Observer{ function update( Observable $observable ); } class SecurityMonitor implements Observer{ function update( Observable $observable ) { $status = $observable->getStatus(); if($status[0] == Login::LOGIN_WRONG_PASS){ echo __CLASS__.":".$status[1]."于".$status[2]."登录失败"; } } } $login = new Login(); $login->attach(new SecurityMonitor()); $login->handleLogin('XXX','XXX','127.0.0.1'); ?>
Running results when an error occurs:
SecurityMonitor:XXX at 127.0. 0.1 Login failed [Finished in 0.1s]
You can see in the code that the login object actively adds the SecurityMonitor object for observation. In this way, to call Login::getStatus(), the SecurityMonitor class must know more information. Although the call occurs on an ObServable object, there is no guarantee that the object is also a Login object. To solve this problem, there is a way: keep the ObServable interface generic intermittently, and the ObServer class is responsible for ensuring that their bodies are of the correct type. They can even add themselves to the subject. The class diagram is as follows:
The implementation code is as follows:
Php code
<?php interface Observable{ function attach( Observer $observer ); function detach( Observer $observer ); function notify(); } class login implements Observable{ const LOGIN_USER_UNKNOW = 1; const LOGIN_WRONG_PASS = 2; const LOGIN_ACCESS = 3; private $status = array(); private $observers = array(); public function setStatus( $status, $user, $ip ) { $this->status = array( $status, $user, $ip ); } public function getStatus() { return $this->status; } public function handleLogin( $user, $pass, $ip ) { switch ( mt_rand( 1, 3 ) ) { case 1: $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip ); $ret = false; break; case 2: $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip ); $ret = false; break; case 3: $this->setStatus( self::LOGIN_ACCESS, $user, $ip ); $ret = true; break; } $this->notify(); return $ret; } public function attach( Observer $observer ) { $this->observers[] = $observer; } public function detach( Observer $observer ) { $newObservers = array(); foreach ( $this->observers as $obs ) { if ( $obs !== $observer ) $newObservers[] = $obs; } $this->observers = $newObservers; } public function notify() { foreach ( $this->observers as $obs ) { $obs->update( $this ); } } } interface Observer{ function update( Observable $observable ); } //以上代码和上例是一样的 abstract class LoginObserver implements Observer{ private $login; public function __construct( Login $login ) { $this->login = $login; $login->attach( $this ); } public function update( Observable $observable ) { if ( $this->login === $observable ) $this->doUpdate( $observable ); } abstract function doUpdate( Login $login ); } class SecurityMonitor extends LoginObserver{ public function doUpdate( Login $login ) { $status = $login->getStatus(); if ( $status[0] == Login::LOGIN_WRONG_PASS ) echo __CLASS__.":".$status[1]."于".$status[2]."登录失败"; } } $login = new Login(); new SecurityMonitor($login);//<strong>此外login对象是被动被观察的</strong> $login->handleLogin( 'XXX', 'XXX', '127.0.0.1' ); ?>
The running result is the same as the above example
After php5, the built-in SPL extension provides native support for the observer mode. After improving the above example through SPL:
Php code
<?php class login implements SplSubject{ const LOGIN_USER_UNKNOW = 1; const LOGIN_WRONG_PASS = 2; const LOGIN_ACCESS = 3; private $status = array(); // private $observers = array(); private $storage; public function __construct() { $this->storage = new SplObjectStorage(); } public function setStatus( $status, $user, $ip ) { $this->status = array( $status, $user, $ip ); } public function getStatus() { return $this->status; } public function handleLogin( $user, $pass, $ip ) { switch ( mt_rand( 1, 3 ) ) { case 1: $this->setStatus( self::LOGIN_USER_UNKNOW, $user, $ip ); $ret = false; break; case 2: $this->setStatus( self::LOGIN_WRONG_PASS, $user, $ip ); $ret = false; break; case 3: $this->setStatus( self::LOGIN_ACCESS, $user, $ip ); $ret = true; break; } $this->notify(); return $ret; } public function attach( SplObserver $observer ) { $this->storage->attach( $observer ); } public function detach( SplObserver $observer ) { $this->storage->detach( $observer ); } public function notify() { foreach ( $this->storage as $obs ) { $obs->update( $this ); } } } abstract class LoginObserver implements SplObserver{ private $login; public function __construct( Login $login ) { $this->login = $login; $login->attach( $this ); } public function update( SplSubject $subject ) { if ( $this->login === $subject ) $this->doUpdate( $subject ); } abstract function doUpdate( Login $login ); } class SecurityMonitor extends LoginObserver{ public function doUpdate( Login $login ) { $status = $login->getStatus(); if ( $status[0] == Login::LOGIN_WRONG_PASS ) echo __CLASS__.":".$status[1]."于".$status[2]."登录失败"; } } $login = new Login(); new SecurityMonitor( $login ); $login->handleLogin( 'XXX', 'XXX', '127.0.0.1' ); ?>
The code has been written, but you still need to understand some theory.
Definition of Observer Pattern
Define a one-to-many dependency relationship between objects, so that whenever an object changes state, all objects that depend on it will be notified and automatically updated. The observer pattern consists of four roles:
1. Subject Observed
Defines the responsibilities that the observed must implement. It must be able to dynamically add and cancel observers. It is generally an abstract class or an implementation class, which only fulfills the responsibilities that must be implemented as an observer, manages observers and passes observers.
2. Observer
After receiving the message, the observer will perform an update operation and process the received information.
3. ConcreteSubject’s specific observable
defines the observable’s own business logic and also defines which events will be notified.
4. ConcreteObserver specific observer
Each observation has a different processing response after receiving the message, and each observer has its own processing logic.
Advantages of the observer pattern
1. There is an abstract coupling between the observer and the observed
Designed in this way, it is very easy to expand whether adding observers or observed objects, and it is already available in java and php The definition of the abstract level of implementation is even more convenient in terms of system expansion.
2. Establish a trigger mechanism
According to the single responsibility principle, each class has a single responsibility. So how to connect each single responsibility into a complex logical relationship in the real world? The observer pattern can perfectly realize the chain form here
Disadvantages of the observer pattern
The observer pattern needs to consider the issues of development efficiency and operating efficiency. One observer, multiple observers, development and Debugging will be more complicated, and in PHP, message notifications are executed sequentially. If an observer is stuck, it will affect the overall execution efficiency. In this case, asynchronous methods are generally considered. The efficiency of multi-stage triggering is even more worrying, so pay attention when designing.
Usage scenarios of observer mode
1. Associated behavior scenarios. It should be noted that the relationship behavior is detachable, not a "combined" relationship
2. Multi-level event triggering scenarios
3. Cross-system message exchange scenarios, such as message queue processing mechanism