zoukankan      html  css  js  c++  java
  • 观察者模式

    问题:
    观察者模式的核心是把客户元素(观察者)从一个中心类(主体)中分离开来。当主体知道事件发生时,观察者需要被通知到。同时,我们并不希望将主体与观察者之间的关系进行硬编码。这样,观察者的代码可被重复使用,不同主体可以随意组合使用多个观察者。

    概念:
    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有观察者对象,使它们能够自动更新自己。

    实现一:

    //主体类接口
    interface Observable
    {
    	public function getStatus();
    	public function attach(Observer $observer);
    	public function detach(Observer $observer);
    	public function notify();
    }
    //主体类实现
    class Login implements Observable
    {
    	const LOGIN_USER_UNKNOWN = 1;
    	const LOGIN_WRONG_PASS = 2;
    	const LOGIN_ACCESS = 3;
    	private $status;
    	private $observers;
    
    	public function __construct()
    	{
    		$this->status = [];
    		$this->observers = [];
    	}
    
    	private function setStatus($status, $user, $ip)
    	{
    		$this->status = [$status, $user, $ip];
    	}
    
    	public function getStatus()
    	{
    		return $this->status;
    	}
    
    	public function handleLogin($user, $pass, $ip)
    	{
    		switch(rand(1, 3)) {
    			case 1:
    				$this->setStatus(self::LOGIN_USER_UNKNOWN, $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 = [];
    		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
    {
    	public function update(Observable $observable);
    }
    //观察者实现
    class SecurityMonitor implements Observer
    {
    	public function update(Observable $observable)
    	{
    		$status = $observable->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Sending mail to sysadmin<br>';
    		}
    	}
    }
    class GeneralLogger implements Observer
    {
    	public function update(Observable $observable)
    	{
    		$status = $observable->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Adding login data to log<br>';
    		}
    	}
    }
    class PartnershipTool implements Observer
    {
    	public function update(Observable $observable)
    	{
    		$status = $observable->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Setting cookie if IP matches a list<br>';
    		}
    	}
    }
    
    //应用
    $login = new Login();
    $login->attach(new SecurityMonitor());
    $login->attach(new GeneralLogger());
    $login->attach(new PartnershipTool());
    $login->handleLogin('ajun', 'shiajunhaha', '127.0.0.1');
    

    注:这里需要在SecurityMonitor、GeneralLogger和PartnershipTool类中调用Login::getStatus(),但是无法保证update()接收到的Observable对象一定是一个Login对象,所以为了保证所有的Observable对象都有getStatus()方法,这里采用扩展Observable接口,并在其中添加了getStatus()方法,这样是可以解决问题,但是降低了Observable接口的通用性。

    实现二:
    保持Observable接口的通用性,由Observer类负责保证它们的主体是正确的类型。
    1. 代码实现:

    //主体类接口
    interface Observable
    {
    	public function attach(Observer $observer);
    	public function detach(Observer $observer);
    	public function notify();
    }
    //主体类实现
    class Login implements Observable
    {
    	const LOGIN_USER_UNKNOWN = 1;
    	const LOGIN_WRONG_PASS = 2;
    	const LOGIN_ACCESS = 3;
    	private $status;
    	private $observers;
    
    	public function __construct()
    	{
    		$this->status = [];
    		$this->observers = [];
    	}
    
    	private function setStatus($status, $user, $ip)
    	{
    		$this->status = [$status, $user, $ip];
    	}
    
    	public function getStatus()
    	{
    		return $this->status;
    	}
    
    	public function handleLogin($user, $pass, $ip)
    	{
    		switch(rand(1, 3)) {
    			case 1:
    				$this->setStatus(self::LOGIN_USER_UNKNOWN, $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 = [];
    		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
    {
    	public 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($observable === $this->login) {
    			$this->doUpdate($observable);
    		}
    	}
    
    	abstract public function doUpdate(Login $login);
    }
    //观察者实现
    class SecurityMonitor extends LoginObserver
    {
    	public function doUpdate(Login $login)
    	{
    		$status = $login->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Sending mail to sysadmin<br>';
    		}
    	}
    }
    class GeneralLogger extends LoginObserver
    {
    	public function doUpdate(Login $login)
    	{
    		$status = $login->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Adding login data to log<br>';
    		}
    	}
    }
    class PartnershipTool extends LoginObserver
    {
    	public function doUpdate(Login $login)
    	{
    		$status = $login->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Setting cookie if IP matches a list<br>';
    		}
    	}
    }
    
    //应用
    $login = new Login();
    new SecurityMonitor($login);
    new GeneralLogger($login);
    new PartnershipTool($login);
    $login->handleLogin('ajun', 'shiajunhaha', '127.0.0.1');
    

    2. 类图:

    实现三:
    通过PHP内置的SPL扩展来实现观察者模式。主要涉及3个元素:SplObserver、SplSubject和SplObjectStorage,其中SplObserver和SplSubject是分别与“实现一”、“实现二”中的Observer和Observable完全相同的接口,而SplObjectStorage则是一个工具类,用于更好地存储、删除对象。
    代码示例:

    //主体类实现
    class Login implements SplSubject
    {
    	const LOGIN_USER_UNKNOWN = 1;
    	const LOGIN_WRONG_PASS = 2;
    	const LOGIN_ACCESS = 3;
    	private $status;
    	private $storage;
    
    	public function __construct()
    	{
    		$this->status = [];
    		$this->storage = new SplObjectStorage();
    	}
    
    	private function setStatus($status, $user, $ip)
    	{
    		$this->status = [$status, $user, $ip];
    	}
    
    	public function getStatus()
    	{
    		return $this->status;
    	}
    
    	public function handleLogin($user, $pass, $ip)
    	{
    		switch(rand(1, 3)) {
    			case 1:
    				$this->setStatus(self::LOGIN_USER_UNKNOWN, $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->attach($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($subject === $this->login) {
    			$this->doUpdate($subject);
    		}
    	}
    
    	abstract public function doUpdate(Login $login);
    }
    //观察者实现
    class SecurityMonitor extends LoginObserver
    {
    	public function doUpdate(Login $login)
    	{
    		$status = $login->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Sending mail to sysadmin<br>';
    		}
    	}
    }
    class GeneralLogger extends LoginObserver
    {
    	public function doUpdate(Login $login)
    	{
    		$status = $login->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Adding login data to log<br>';
    		}
    	}
    }
    class PartnershipTool extends LoginObserver
    {
    	public function doUpdate(Login $login)
    	{
    		$status = $login->getStatus();
    		if($status[0] == Login::LOGIN_WRONG_PASS) {
    			echo 'Setting cookie if IP matches a list<br>';
    		}
    	}
    }
    
    //应用
    $login = new Login();
    new SecurityMonitor($login);
    new GeneralLogger($login);
    new PartnershipTool($login);
    $login->handleLogin('ajun', 'shiajunhaha', '127.0.0.1');
    

    效果:
    1. 当一个对象的改变需要同时改变其他对象,而且不知道具体有多少对象需要被改变的时候,就应该考虑使用观察者模式。
    2. 观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一方。

  • 相关阅读:
    5分钟GET我使用Github 5 年总结的这些骚操作!
    这个项目可以让你在几分钟快速了解某个编程语言
    vulnstack靶机实战01
    PHP代码审计04之strpos函数使用不当
    【T1543.003】利用 ACL 隐藏恶意 Windows 服务
    Baolu CSV Data Set Config
    两种方式简单免杀ew
    mssql dba注入点写shell
    jeecms写shell的两种方法
    三种方式获取SSMS连接密码
  • 原文地址:https://www.cnblogs.com/wujuntian/p/9657951.html
Copyright © 2011-2022 走看看