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

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

作者: 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