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

    观察者模式,也称发布-订阅模式,定义了一个被观察者和多个观察者的、一对多的对象关系。

    在被观察者状态发生变化的时候,它的所有观察者都会收到通知,并自动更新。

    观察者模式通常用在实时事件处理系统、组件间解耦、数据库驱动的消息队列系统,同时也是MVC设计模式中的重要组成部分。

    以下我们以订单创建为例。

    当订单创建后,系统会发送邮件和短信,并保存日志记录。

    1 问题

    在没有用观察者模式的时候,如下:

    class Order
    {
        // 订单状态
        private $state = 0;
    
        // 订单状态有变化时发送通知
        public function addOrder()
        {
            $this->state = 1;
            // 发送邮件
            Email::update($this->state);
            // 短信通知
            Message::update($this->state);
            // 记录日志
            Log::update();
            // 其他更多通知
        }
    }

    代码中,在Order类中调用各类的方法来实现通知。当在客户端创建订单:

    $order = new Order();
    $order->addOrder();

    就会同时产生三个通知:发送邮件、发送短信和记录日志。

    在系统小的时候,这是非常快捷有效的方式。

    可是,当系统变大的时候,这种方法马上面临难以扩展的问题,并且容易出错:

    1. 如果订单不需要某种通知,比如不需要记录日志,则必须修改Order类,做状态的判断;
    2. 如果再加一种通知方式,如系统消息通知,则除了增加新类,同时还需要修改Order类和客户端。

    这两条都不符合面向对象中的开闭原则,会让系统越来越难维护。

    2 解决

    接下来我们用观察者模式解决这个问题。

    2.1 被观察者

    被观察者是一些具体的实例,比如订单管理、用户登陆、评论回复、状态审核等等。

    别的功能会依赖于它们的状态进行各种动作。

    /**
     * 被观察者接口
     */
    interface Observable
    {
        // 添加/注册观察者
        public function attach(Observer $observer);
        // 删除观察者
        public function detach(Observer $observer);
        // 触发通知
        public function notify();
    }
    
    /**
     * 被观察者
     * 职责:添加观察者到$observers属性中,
     * 有变动时通过notify()方法运行通知
     */
    class Order implements Observable
    {
        // 保存观察者
        private $observers = array();
        // 订单状态
        private $state = 0;
    
        // 添加(注册)观察者
        public function attach(Observer $observer)
        {
            $key = array_search($observer, $this->observers);
            if ($key === false) {
                $this->observers[] = $observer;
            }
        }
    
        // 移除观察者
        public function detach(Observer $observer)
        {
            $key = array_search($observer, $this->observers);
            if ($key !== false) {
                unset($this->observers[$key]);
            }
        }
    
        // 遍历调用观察者的update()方法进行通知,不关心其具体实现方式
        public function notify()
        {
            foreach ($this->observers as $observer) {
                // 把本类对象传给观察者,以便观察者获取当前类对象的信息
                $observer->update($this);
            }
        }
    
        // 订单状态有变化时发送通知
        public function addOrder()
        {
            $this->state = 1;
            $this->notify();
        }
    
        // 获取提供给观察者的状态
        public function getState()
        {
            return $this->state;
        }
    }

    被观察者至少要实现attach()detach()notify()三个方法,用以添加、删除和通知观察者。

    通知的方式是,在类中的其他方法(如:创建订单)调用notify()方法。

    另外,观察者可能用到被观察者的一些状态信息。

    所以,要在notify()中把当前对象作为参数传给观察者,方便其通过提供的public方法获得被观察者的状态信息。

    本例用getState()方法供给观察者获取状态信息。

    2.2 观察者

    观察者可能有多个,但每个观察者都必须实现Observer接口规定的update()方法,这是接收被观察者通知的唯一渠道。

    /**
     * 观察者接口
     */
    interface Observer
    {
        // 接收到通知的处理方法
        public function update(Observable $observable);
    }
    
    /**
     * 观察者1:发送邮件
     */
    class Email implements Observer
    {
        public function update(Observable $observable)
        {
            $state = $observable->getState();
            if ($state) {
                echo '发送邮件:您已经成功下单。';
            } else {
                echo '发送邮件:下单失败,请重试。';
            }
        }
    }
    
    /**
     * 观察者2:短信通知
     */
    class Message implements Observer
    {
        public function update(Observable $observable)
        {
            $state = $observable->getState();
            if ($state) {
                echo '短信通知:您已下单成功。';
            } else {
                echo '短信通知:下单失败,请重试。';
            }
        }
    }
    
    /**
     * 观察者3:记录日志
     */
    class Log implements Observer
    {
        public function update(Observable $observable)
        {
            echo '记录日志:生成了一个订单记录。';
        }
    }

    这里有三个观察者:发送邮件、短信通知和记录日志,它们都实现了update()方法。

    其中,发送邮件和短信依赖于订单、也就是被观察者的状态,来决定发送消息的内容,记录日志则不需要订单的状态。

    2.3 客户端

    然后我们创建一个客户端,内容:

    // 创建观察者对象
    $email = new Email();
    $message = new Message();
    $log = new Log();
    // 创建订单对象
    $order = new Order();
    
    // 向订单对象中注册3个观察者:发送邮件、短信通知、记录日志
    $order->attach($email);
    $order->attach($message);
    $order->attach($log);
    // 添加订单,添加时会自动发送通知给观察者
    $order->addOrder();
    
    echo '<br />';
    
    // 删除记录日志观察者
    $order->detach($log);
    // 添加另一个订单,会再次发送通知给观察着
    $order->addOrder();

    执行应用后,会输出这样的消息:

    发送邮件:您已经成功下单。添加日志:生成了一个订单记录。系统消息:您已下单成功。
    发送邮件:您已经成功下单。添加日志:生成了一个订单记录。

    对于不需要通知的观察者,用detach()移出观察者列表即可。

    这种情况就解开了类之间的耦合。

    2.4 新增观察者

    如果再需要新增一个观察者,如下,只需要添加观察者类本身,实现update()方法。

    /**
     * 观察者4:系统消息
     */
    class Alert implements Observer
    {
        public function update(Observable $observable)
        {
            echo '系统消息:您的订单有更新了~~~';
        }
    }

    再到客户端中注册Alert类到观察者列表中:

    // 创建“系统消息”观察者
    $alert = new Alert();
    // 注册观察者到订单对象中
    $order->attach($alert);

    就能订阅被观察者的通知。

    3 特点

    在观察者模式中,被观察者完全不需要关心观察者,在自身状态有变化是,遍历执行观察者update()方法即完成通知。

    在观察者模式中,被观察者通过添加attach()方法,提供给观察者注册,使自己变得可见。

    当被观察者改变时,给注册的观察者发送通知。至于观察者如何处理通知,被观察者不需要关心。

    这是一种良好的设计,对象之间不必相互理解,同样能够相互通信。

    UML如下:

    面向对象编程中,任何对象的状态都非常重要,它们是对象间交互的桥梁。

    当一个对象的改变需要被其他对象关注时,观察者模式就派上用场了。

  • 相关阅读:
    Linux异步IO
    基本数据类型总结--
    总结
    字典魔法二
    字典及其魔法
    元祖的魔法
    列表的特点
    运算符
    while ……else……和while……continue……和 while…………break…………
    作业---写一个程序,用户名 、密码输入错误3次 错误
  • 原文地址:https://www.cnblogs.com/-mrl/p/13282602.html
Copyright © 2011-2022 走看看