美文网首页
观察者模式(发布订阅模式)

观察者模式(发布订阅模式)

作者: 747大雄 | 来源:发表于2019-10-09 13:53 被阅读0次

    定义

    • 观察者模式

      一个或多个观察者对目标的状态感兴趣,通过将自己依附在目标对象上以便注册所感兴趣的内容。目标状态发生改变并且观察者可能对这些改变感兴趣,会发送一个通知消息,调用每个观察者的更新方法。当观察者不再对目标状态感兴趣时,他们可以简单将自己从中分离。

    • 发布订阅模式

      定义相同,就是发布订阅模式有个事件调度中心。

    区别

    a.png

    从图中可以看出,观察者模式中观察者和目标直接进行交互,而发布订阅模式中统一由调度中心进行处理,订阅者和发布者互不干扰。这样一方面实现了解耦,还有就是可以实现更细粒度的一些控制。比如发布者发布了很多消息,但是不想所有的订阅者都接收到,就可以在调度中心做一些处理,类似于权限控制之类的。还可以做一些节流操作。

    简单示例

    • 观察者模式

      // 观察者
      class Observer {
          constructor() {
      
          }
          update(val) {
      
          }
      }
      // 观察者列表
      class ObserverList {
          constructor() {
              this.observerList = []
          }
          add(observer) {
              return this.observerList.push(observer);
          }
          remove(observer) {
              this.observerList = this.observerList.filter(ob => ob !== observer);
          }
          count() {
              return this.observerList.length;
          }
          get(index) {
              return this.observerList[index];
          }
      }
      // 目标
      class Subject {
          constructor() {
              this.observers = new ObserverList();
          }
          addObserver(observer) {
              this.observers.add(observer);
          }
          removeObserver(observer) {
              this.observers.remove(observer);
          }
          notify(...args) {
              let obCount = this.observers.count();
              for (let index = 0; index < obCount; index++) {
                  this.observers.get(i).update(...args);
              }
          }
      }
      
    • 发布订阅模式

      class PubSub {
          constructor() {
              this.subscribers = {}
          }
          subscribe(type, fn) {
              if (!Object.prototype.hasOwnProperty.call(this.subscribers, type)) {
                this.subscribers[type] = [];
              }
              
              this.subscribers[type].push(fn);
          }
          unsubscribe(type, fn) {
              let listeners = this.subscribers[type];
              if (!listeners || !listeners.length) return;
              this.subscribers[type] = listeners.filter(v => v !== fn);
          }
          publish(type, ...args) {
              let listeners = this.subscribers[type];
              if (!listeners || !listeners.length) return;
              listeners.forEach(fn => fn(...args));        
          }
      }
      
      let ob = new PubSub();
      ob.subscribe('add', (val) => console.log(val));
      ob.publish('add', 1);
      

      在观察者模式中,观察者是知道Subject的,Subject也一直保持对观察者进行记录。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。

      观察者模式大多数时候是同步的,比如当事件触发,Subject就会去调用观察者的方法。而发布-订阅模式大多数时候是异步的(使用消息队列)。

    WEB开发应用应用场景

    • 购票流程开发中,当购票完成后,需要记录文本日志,发送短信,赠送积分等等活动,传统是冗余在一个模块中。

      存在的问题就是一旦某个业务逻辑发生改变,如购票业务中增加其他业务逻辑,需要修改购票核心文件、甚至购票流程。日积月累后,文件冗长,导致后续维护困难。

      为了解决这种机密耦合的编程方式,使用观察模式将目前的业务逻辑优化成"松耦合",达到易维护、易修改的目的

      #===================定义观察者、被观察者接口============
      /**
       * 观察者接口(通知接口)
       */
      
      interface ITicketObserver //观察者接口
      {
          function onBuyTicketOver($sender, $args); //得到通知后调用的方法
      }
      /**
        * 主题接口
       */
      interface ITicketObservable //被观察对象接口
      {
          function addObserver($observer); //提供注册观察者方法
      }
      
      #====================主题类实现========================
      /**
       * 主题类(购票)
       */
      class HipiaoBuy implements ITicketObservable { //实现主题接口(被观察者)
          private $_observers = array (); //通知数组(观察者)
          public function buyTicket($ticket) //购票核心类,处理购票流程
          {       
              // TODO购票逻辑
              
             //循环通知,调用其onBuyTicketOver实现不同业务逻辑
             foreach ( $this->_observersas $obs )
                 $obs->onBuyTicketOver ( $this, $ticket ); //$this 可用来获取主题类句柄,在通知中使用
          }
          //添加通知
          public function addObserver($observer) //添加N个通知
        {
             $this->_observers [] = $observer;
          }
      }
      
      #=========================定义多个通知====================
      //短信日志通知
      class HipiaoMSM implements ITicketObserver {
          public function onBuyTicketOver($sender, $ticket) {
             echo (date ( 'Y-m-d H:i:s' ) . " 短信日志记录:购票成功:$ticket<br>");
          }
      }
      
      //文本日志通知
      class HipiaoTxt implements ITicketObserver {
          public function onBuyTicketOver($sender, $ticket) {
             echo (date ( 'Y-m-d H:i:s' ) . " 文本日志记录:购票成功:$ticket<br>");
          }
      }
      
      //抵扣卷赠送通知
      class HipiaoDiKou implements ITicketObserver {
          public function onBuyTicketOver($sender, $ticket) {
             echo (date ( 'Y-m-d H:i:s' ) . " 赠送抵扣卷:购票成功:$ticket赠送10元抵扣卷1张。<br>");
          }
      }
      
      #============================用户购票====================
      
      $buy = new HipiaoBuy ();
      $buy->addObserver ( new HipiaoMSM () ); //根据不同业务逻辑加入各种通知
      $buy->addObserver ( new HipiaoTxt () );
      $buy->addObserver ( new HipiaoDiKou () );
      
      //购票
      $buy->buyTicket ( "一排一号" );
      

    总结

    • 优点

      1、观察者和被观察者是抽象耦合的。 2、建立一套触发机制。

    • 缺点

      1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 3、如果某些观察者的响应方法被阻塞,整个通知过程即被阻塞,其它观察者不能及时被通知(异步?)

    命令模式

    定义

    在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。但在某些场合,比如要对行为进行“记录、撤销/重做、事物”等处理,这种无法抵御变化的紧耦合是不合适的。将一组行为抽象为对象,实现二者之间的松耦合,这就是命令模式。

    在命令的发布者和接收者之间,定义一个命令对象,命令对象暴露出一个统一的接口给命令的发布者,而命令的发布者不用去管接收者是如何执行命令的,做到命令发布者和接收者的解耦。

    简单示例

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>cmd-demo</title>
    </head>
    <body>
        <div>
            <button id="btn1">按钮1</button>
            <button id="btn2">按钮2</button>
            <button id="btn3">按钮3</button>
        </div>
        <script>
            var btn1 = document.getElementById('btn1')
            var btn2 = document.getElementById('btn2')
            var btn3 = document.getElementById('btn3')
    
            // 定义一个命令发布者(执行者)的类
            class Executor {
                setCommand(btn, command) {
                    btn.onclick = function() {
                        command.execute()
                    }
                }
            }
    
            // 定义一个命令接收者
            class Menu {
                refresh() {
                    console.log('刷新菜单')
                }
    
                addSubMenu() {
                    console.log('增加子菜单')
                }
            }
    
            // 定义一个刷新菜单的命令对象的类
            class RefreshMenu {
                constructor(receiver) {
                    // 命令对象与接收者关联
                    this.receiver = receiver
                }
    
                // 暴露出统一的接口给命令发布者Executor
                execute() {
                    this.receiver.refresh()
                }
            }
    
            // 定义一个增加子菜单的命令对象的类
            class AddSubMenu {
                constructor(receiver) {
                    // 命令对象与接收者关联
                    this.receiver = receiver
                }
                // 暴露出统一的接口给命令发布者Executor
                execute() {
                    this.receiver.addSubMenu()
                }
            }
    
            var menu = new Menu()
            var executor = new Executor()
    
            var refreshMenu = new RefreshMenu(menu)
            // 给按钮1添加刷新功能
            executor.setCommand(btn1, refreshMenu)
    
            var addSubMenu = new AddSubMenu(menu)
            // 给按钮2添加增加子菜单功能
            executor.setCommand(btn2, addSubMenu)
            
            // 如果想给按钮3增加删除菜单的功能,就继续增加删除菜单的命令对象和接收者的具体删除方法,而不必修改命令对象
        </script>
    </body>
    </html>
    

    WEB开发应用示例

    总结

    命令模式的主要优点如下。

    1. 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
    2. 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
    3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
    4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。

    其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性。

    相关文章

      网友评论

          本文标题:观察者模式(发布订阅模式)

          本文链接:https://www.haomeiwen.com/subject/aefgpctx.html