设计模式-----观察者模式

一、概念

1、Observer模式的概念

(Observer)被称作发布-订阅者模式或消息机制,定义了一种依赖关系,解决了主体对象与观察者之间功能的耦合。

(Observer)模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。

好处:

  • 可广泛应用于异步编程中,是一种替代传递回调函数的方案。
  • 可取代对象之间硬编码的通知机制,一个对象不用再显示地调用另外一个对象的某个接口。 两对象轻松解耦。

2、Observer模式的角色

Subject(被观察者)

被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。

ConcreteSubject -- 被观察者的具体实现。包含一些基本的属性状态及其他操作。

Observer(观察者)

接口或抽象类。当Subject的状态发生变化时,Observer对像将通过一个callback函数得到通知。

ConcreteObserver -- 观察者的具体实现。得到通知后将完成一些具体的业务逻辑处理。

二、 应用场景

javascript 复制代码
<script>  
let subscribe = { 'eating': [] };
    // 订阅
    subscribe.eating[0] = () => {
      console.log('我是Ben,我要去吃饭');
    }
    subscribe.eating[1] = () => {
      console.log('我是James,我要去吃饭');
    }
    subscribe.eating[2] = () => {
      console.log('我是Oscar,我要去吃饭');
    }
    // 发布
    let treat = (() => {
      let eatingPersion = subscribe.eating;
      let len = eatingPersion.length;
      for (let i = 0; i < len; i++) {
        subscribe.eating[i]();
      }
    })();
    /*
      如果Judy也想去吃饭的话,不需要告诉任何人, 只需要自己订阅就好啦。
      But这里有个缺陷,就是Ben今天想减肥,想要取消订阅,没有提供取消订阅的的机制
    */
    // 完善先取消订阅模式
    var Observer = (function () {
      //静态私有变量,用来存放不对外暴露的消息列队
      var _rank = {};
      return {
        //订阅消息接口
        register: function (type, fn) {
          //如果这个消息的方法不存在,则放入消息列队中
          if (typeof _rank[type] === 'undefined') {
            _rank[type] = [fn];
          }
          //如果这个消息的方法是存在的,就给这个消息的数组中添加这个方法
          else {
            _rank[type].push(fn);
          }
        },
        //发布消息接口
        fire: function (type, argsJson) {
          //如果消息未定义,就返回
          if (!_rank[type]) { return; }
          //对消息的参数进行包装
          var newArgs = {
            type: type,
            args: argsJson || {}
          };
          //遍历执行这个消息列队中的方法
          for (var i = 0, l = _rank[type].length; i < l; i++) {
            _rank[type][i].call(this, newArgs);
          }
        },
        //消息注销方法
        remove: function (type, fn) {
          //确保这个消息列队是存在的
          if (_rank[type] instanceof Array) {
            //从最后一个方法开始查找
            for (var i = _rank[type].length - 1; i >= 0; i--) {
              //如果存在就从列队中删除
              _rank[type][i].toString() === fn.toString() && _rank[type].splice(i, 1);
            }
          }
        }
      }
    })();
    // PM瑞瑞发布
    Observer.fire('eating', { msg: 'pm瑞瑞请客~' });
    // 订阅
    Observer.register('eating', function (params) {
      console.log(params.type + ': 我是Ben,我要去吃饭,' + params.args.msg);
    });
    Observer.register('eating', function (params) {
      console.log(params.type + ': 我是James,我要去吃饭,' + params.args.msg);
    });
    Observer.register('eating', function (params) {
      console.log(params.type + ': 我是Oscar,我要去吃饭,' + params.args.msg);
    });
    // Ben取消订阅
    Observer.remove('eating', function (params) {
      console.log(params.type + ': 我是Ben,我要去吃饭,' + params.args.msg);
    });
    // PM瑞瑞重新发布
    Observer.fire('eating',{msg:'pm瑞瑞请客~'});

    /*
      此时,如果还有人要吃饭,只需要订阅一个位置就好,并且其它的活动也可以通过模式去订阅,
      发布,撤销~。观察者的使用环境是这样的,例如:多人协作中在初始化函数init()中不同人
      都要加事件,如果直接添加,总是怕出问题,此时,如果通过定义一个观察者,
      就方便多了,可以放心大胆的操作。又例如年久失修的代码要更改,但是关系错中复杂,最好的
      方式就是给事件都归入消息列队,然后添加自己的事件,这样就万事大吉了~
    */
 </script>
javascript 复制代码
/**
  * 发布订阅模式(观察者模式)
  * handles: 
  * on: 订阅事件
  * emit: 发布事件
  * off: 删除事件
 **/
class PubSub {
  constructor() {
    this.handles = {} // 事件处理函数集合
  }
  //订阅事件
  on(eventType, handle) {
    if (!this.handles.hasOwnProperty(eventType)) {
      this.handles[eventType] = []
    }
    if (typeof handle == 'function') {
      this.handles[eventType].push()
    } else {
      throw new Error('缺少回调函数')
    }
    return this;
  }
  // 发布事件
  emit(eventType, ...args) {
    if (this.handles.hasOwnProperty(eventType)) {
      this. Handles[eventType].forEach((item, key, arr) => {
        item.apply(null, args)
      })
    } else {
      throw new Error(`"${eventType}"事件未注册`)
    }
    return this;
  }
  // 删除事件
  off(eventType, handle) {
    if (!this.handles.hasOwnProperty(eventType)) {
      throw new Error(`"${eventType}"事件未注册`)
    } else if (typeof handle != 'function') {
      throw new Error('缺少回调函数')
    } else {
      this.handles[eventType].forEach((item, key, arr) => {
        if (item == handle) {
          this.handles[eventType].splice(key, 1)
        }
      })
    }
    return this;
  }
}
let pubsub = new PubSub();
function callback() {
  console.log('you are so nice')
}
// 订阅:如下在事件eventTypeName上添加了两个回调
pubsub.on('eventTypeName', (...args) => {
  console.log(args.join(' '))
}).on('eventTypeName', callback)
// 发布:
pubsub.emit('eventTypeName', 'whar', 'a', 'fucking day'); //发布 依次执行 eventTypeName 事件中的 所以方法
pubsub.off('eventTypeName', callback)  // 删除只是删除 eventTypeName 事件的 callback 方法
pubsub.emit('eventTypeName', 'fucking', 'again')
/*
  输出值:
  what a fucking day
  you are so nice
  fucking again
*/

总结: 发布---订阅模式的优点非常明显,一为时间上的解耦,二为对象之间的解耦。它的应用非常广泛,既可以 用在异步编程中,也可以帮助我们完成更松耦合的代码编写。发布---订阅模式还可以用来帮助实现一些别 的设计模式,比如中介者模式。从架构上来看,无论是 MVC 还是 MVVM, 都少不了发布---订阅模式的参 与,而且JavaScript本身也是一门基于事件驱动的语言。 当然,发布---订阅模式也不是完全没有缺点(浪费内存)。创建订阅者本身要消耗一定的时间和内存, 而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中。另外,发 布---订阅模式虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被 深埋在背后,会导致程序难以跟踪维护和理解。特别是有多个发布者和订阅者(b订阅a的消息并发布 给c)嵌套到一起的时候,要跟踪一个bug不是件轻松的事情。

相关推荐
Damon_X41 分钟前
桥接模式(Bridge Pattern)
设计模式·桥接模式
越甲八千5 小时前
重温设计模式--享元模式
设计模式·享元模式
码农爱java6 小时前
设计模式--抽象工厂模式【创建型模式】
java·设计模式·面试·抽象工厂模式·原理·23种设计模式·java 设计模式
越甲八千7 小时前
重温设计模式--中介者模式
windows·设计模式·中介者模式
犬余7 小时前
设计模式之桥接模式:抽象与实现之间的分离艺术
笔记·学习·设计模式·桥接模式
Theodore_10228 小时前
1 软件工程——概述
java·开发语言·算法·设计模式·java-ee·软件工程·个人开发
越甲八千10 小时前
重拾设计模式--组合模式
设计模式·组合模式
思忖小下13 小时前
梳理你的思路(从OOP到架构设计)_设计模式Composite模式
设计模式·组合模式·eit
机器视觉知识推荐、就业指导13 小时前
C++设计模式:组合模式(公司架构案例)
c++·后端·设计模式·组合模式
越甲八千13 小时前
重拾设计模式--工厂模式(简单、工厂、抽象)
c++·设计模式