JavaScript设计模式之观察者模式与发布订阅模式详解

本文实例讲述了JavaScript设计模式之观察者模式与发布订阅模式。分享给大家供大家参考,具体如下:

学习了一段时间设计模式,当学到观察者模式和发布订阅模式的时候遇到了很大的问题,这两个模式有点类似,有点傻傻分不清楚,博客起因如此,开始对观察者和发布订阅开始了Google之旅。对整个学习过程做一个简单的记录。

观察者模式

当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。在观察模式中共存在两个角色观察者(Observer)被观察者(Subject),然而观察者模式在软件设计中是一个对象,维护一个依赖列表,当任何状态发生改变自动通知它们。

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

在观察者模式中一共分为这么几个角色:

  1. Subject:维护一系列观察者,方便添加或删除观察者
  2. Observer:为那些在目标状态发生改变时需要获得通知的对象提供一个更新接口
  3. ConcreteSuject:状态发生改变时,想Observer发送通知,存储ConcreteObserver的状态
  4. ConcreteObserver:具体的观察者
举例

举一个生活中的例子,公司老板可以为下面的工作人员分配认为,如果老板作为被观察者而存在,那么下面所属的那些员工则就作为观察者而存在,为工作人员分配的任务来通知下面的工作人员应该去做哪些工作。

通过上面的例子可以对观察者模式有一个简单的认知,接下来结合下面的这张图来再次分析一下上面的例子。

如果Subject = 老板的话,那么Observer N = 工作人员,如果细心观察的话会发现下图中莫名到的多了一个notify(),那么上述例子中的工作就是notify()

既然各个关系已经屡清楚了,下面通过代码来实现一下上述的例子:

// 观察者队列
class ObserverList{
  constructor(){
    this.observerList = {};
  }
  Add(obj,type = "any"){
    if(!this.observerList[type]){
      this.observerList[type] = [];
    }
    this.observerList[type].push(obj);
  }
  Count(type = "any"){
    return this.observerList[type].length;
  }
  Get(index,type = "any"){
    let len = this.observerList[type].length;
    if(index > -1 && index < len){
      return this.observerList[type][index]
    }
  }
  IndexOf(obj,startIndex,type = "any"){
    let i = startIndex,
      pointer = -1;
    let len = this.observerList[type].length;
    while(i < len){
      if(this.observerList[type][i] === obj){
        pointer = i;
      }
      i++;
    }
    return pointer;
  }
  RemoveIndexAt(index,type = "any"){
    let len = this.observerList[type].length;
    if(index === 0){
      this.observerList[type].shift();
    }
    else if(index === len-1){
      this.observerList[type].pop();
    }
    else{
      this.observerList[type].splice(index,1);
    }
  }
}
// 老板
class Boos {
  constructor(){
    this.observers = new ObserverList();
  }
  AddObserverList(observer,type){
    this.observers.Add(observer,type);
  }
  RemoveObserver(oberver,type){
    let i = this.observers.IndexOf(oberver,0,type);
    (i != -1) && this.observers.RemoveIndexAt(i,type);
  }
  Notify(type){
    let oberverCont = this.observers.Count(type);
    for(let i=0;i<oberverCont;i++){
      let emp = this.observers.Get(i,type);
      emp && emp(type);
    }
  }
}
class Employees {
 constructor(name){
  this.name = name;
 }
 getName(){
  return this.name;
 }
}
class Work {
 married(name){
  console.log(`${name}上班`);
 }
 unemployment(name){
  console.log(`${name}出差`);
 }
 writing(name){
  console.log(`${name}写作`);
 }
 writeCode(name){
  console.log(`${name}打代码`);
 }
}
let MyBoos = new Boos();
let work = new Work();
let aaron = new Employees("Aaron");
let angie = new Employees("Angie");
let aaronName = aaron.getName();
let angieName = angie.getName();
MyBoos.AddObserverList(work.married,aaronName);
MyBoos.AddObserverList(work.writeCode,aaronName);
MyBoos.AddObserverList(work.writing,aaronName);
MyBoos.RemoveObserver(work.writing,aaronName);
MyBoos.Notify(aaronName);

MyBoos.AddObserverList(work.married,angieName);
MyBoos.AddObserverList(work.unemployment,angieName);
MyBoos.Notify(angieName);
// Aaron上班
// Aaron打代码
// Angie上班
// Angie出差

代码里面完全遵循了流程图,Boos类作为被观察者而存在,Staff作为观察者,通过Work两者做关联。

如果相信的阅读上述代码的话可以出,其实观察者的核心代码就是peopleList这个对象,这个对象里面存放了N多个Array数组,通过run方法触发观察者的notify队列。观察者模式主要解决的问题就是,一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。当我们在做程序设计的时候,当一个目标对象的状态发生改变,所有的观察者对象都将得到通知,进行广播通知的时候,就可以使用观察者模式啦。

优点
  1. 观察者和被观察者是抽象耦合的。
  2. 建立一套触发机制。
缺点
  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
小结

对于观察者模式在被观察者中有一个用于存储观察者对象的list队列,通过统一的方法触发,目标和观察者是基类,目标提供维护观察者的一系列方法,观察者提供更新接口。具体观察者和具体目标继承各自的基类,然后具体观察者把自己注册到具体目标里,在具体目标发生变化时候,调度观察者的更新方法。

发布/订阅模式

在发布订阅模式上卡了很久,但是废了好长时间没有搞明白,也不知道自己的疑问在哪,于是就疯狂Google不断地翻阅找到自己的疑问,个人觉得如果想要搞明白发布订阅模式首先要搞明白谁是发布者,谁是订阅者。

发布订阅:在软件架构中,发布-订阅是一种消息范式,消息的发送者(称为发布者)不会将消息直接发送给特定的接收者(称为订阅者)。而是将发布的消息分为不同的类别,无需了解哪些订阅者(如果有的话)可能存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者(如果有的话)存在。-- 维基百科

看了半天没整明白(✿◡‿◡),惭愧...于是,学习的路途不能止步,继续...

大概很多人都和我一样,觉得发布订阅模式里的Publisher,就是观察者模式里的Subject,而Subscriber,就是ObserverPublisher变化时,就主动去通知Subscriber。其实并不是。在发布订阅模式里,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识。互不相识?那他们之间如何交流?

答案是,通过第三者,也就是在消息队列里面,我们常说的经纪人Broker

发布者只需告诉Broker,我要发的消息,topicAAA,订阅者只需告诉Broker,我要订阅topicAAA的消息,于是,当Broker收到发布者发过来消息,并且topicAAA时,就会把消息推送给订阅了topicAAA的订阅者。当然也有可能是订阅者自己过来拉取,看具体实现。

也就是说,发布订阅模式里,发布者和订阅者,不是松耦合,而是完全解耦的。

通过上面的描述终于有了一些眉目,再举一个生活中的例子,就拿微信公众号来说,每次微信公众号推送消息并不是一下子推送给微信的所有用户,而是选择性的推送给那些已经订阅了该公众号的人。

老规矩吧,用代码实现一下:

class Utils {
 constructor(){
  this.observerList = {};
 }
 Add(obj,type = "any"){
  if(!this.observerList[type]){
   this.observerList[type] = [];
  }
  this.observerList[type].push(obj);
 }
 Count(type = "any"){
  return this.observerList[type].length;
 }
 Get(index,type = "any"){
  let len = this.observerList[type].length;
  if(index > -1 && index < len){
   return this.observerList[type][index]
  }
 }
 IndexOf(obj,startIndex,type = "any"){
  let i = startIndex,
    pointer = -1;
  let len = this.observerList[type].length;
  while(i < len){
   if(this.observerList[type][i] === obj){
    pointer = i;
   }
   i++;
  }
  return pointer;
 }
}
// 订阅者
class Subscribe extends Utils {};
// 发布者
class Publish extends Utils {};
// 中转站
class Broker {
 constructor(){
  this.publish = new Publish();
  this.subscribe = new Subscribe();
 }
 // 订阅
 Subscribe(fn,key){
  this.subscribe.Add(fn,key);
 }
 // 发布
 Release(fn,key){
  this.publish.Add(fn,key);
 }
 Run(key = "any"){
  let publishList = this.publish.observerList;
  let subscribeList = this.subscribe.observerList;
  if(!publishList[key] || !subscribeList[key]) throw "No subscribers or published messages";
  let pub = publishList[key];
  let sub = subscribeList[key];
  let arr = [...pub,...sub];
  while(arr.length){
   let item = arr.shift();
   item(key);
  }
 }
}
class Employees {
 constructor(name){
  this.name = name;
 }
 getName(){
  let {name} = this;
  return name;
 }
 receivedMessage(key,name){
  console.log(`${name}收到了${key}发来的消息`);
 }
}
class Public {
 constructor(name){
  this.name = name;
 }
 getName(){
  let {name} = this;
  return name;
 }
 sendMessage(key){
  console.log(`${key}发送了一条消息`);
 }
}
let broker = new Broker();
let SundayPublic = new Public("Sunday");
let MayPublic = new Public("May");
let Angie = new Employees("Angie");
let Aaron = new Employees("Aaron");
broker.Subscribe(() => {
 Angie.receivedMessage(SundayPublic.getName(),Angie.getName());
},SundayPublic.getName());
broker.Subscribe(() => {
 Angie.receivedMessage(SundayPublic.getName(),Aaron.getName());
},SundayPublic.getName());
broker.Subscribe(() => {
 Aaron.receivedMessage(MayPublic.getName(),Aaron.getName());
},MayPublic.getName());
broker.Release(MayPublic.sendMessage,MayPublic.getName());
broker.Release(SundayPublic.sendMessage,SundayPublic.getName());
broker.Run(SundayPublic.getName());
broker.Run(MayPublic.getName());
// Sunday发送了一条消息
// Angie收到了Sunday发来的消息
// Aaron收到了Sunday发来的消息
// May发送了一条消息
// Aaron收到了May发来的消息

通过上面的输出结果可以得出,只要订阅过该公众号的用户,只要公众号发送一条消息,所有订阅过该条消息的用户都是可以收到这条消息。虽然代码有点多,但是确确实实能够体现发布订阅模式的魅力,很不错。

优点
  1. 支持简单的广播通信,当对象状态发生改变时,会自动通知已经订阅过的对象。
  2. 发布者与订阅者耦合性降低,发布者只管发布一条消息出去,它不关心这条消息如何被订阅者使用,同时,订阅者只监听发布者的事件名,只要发布者的事件名不变,它不管发布者如何改变;同理卖家(发布者)它只需要将鞋子来货的这件事告诉订阅者(买家),他不管买家到底买还是不买,还是买其他卖家的。只要鞋子到货了就通知订阅者即可。
缺点
  1. 创建订阅者需要消耗一定的时间和内存。
  2. 虽然可以弱化对象之间的联系,如果过度使用的话,反而使代码不好理解及代码不好维护。
小结

发布订阅模式可以降低发布者与订阅者之间的耦合程度,两者之间从来不关系你是谁,你要作什么?订阅者只需要跟随发布者,若发布者发生变化就会通知订阅者应该也做出相对于的变化。发布者与订阅者之间不存在直接通信,他们所有的一切事情都是通过中介者相互通信,它过滤所有传入的消息并相应地分发它们。发布订阅模式可用于在不同系统组件之间传递消息的模式,而这些组件不知道关于彼此身份的任何信息。

观察者模式与发布订阅的区别

  1. Observer模式中,Observers知道Subject,同时Subject还保留了Observers的记录。然而,在发布者/订阅者中,发布者和订阅者不需要彼此了解。他们只是在消息队列或代理的帮助下进行通信。
  2. Publisher / Subscriber模式中,组件是松散耦合的,而不是Observer模式。
  3. 观察者模式主要以同步方式实现,即当某些事件发生时,Subject调用其所有观察者的适当方法。发布者/订阅者在大多情况下是异步方式(使用消息队列)。
  4. 观察者模式需要在单个应用程序地址空间中实现。另一方面,发布者/订阅者模式更像是跨应用程序模式。

如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的。如果以意图来分辨模式,他们都是实现了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新,那么他们就是同一种模式,发布订阅模式是在观察者模式的基础上做的优化升级。在观察者模式中,观察者需要直接订阅目标事件。在目标发出内容改变的事件后,直接接收事件并作出响应。发布订阅模式相比观察者模式多了个事件通道,订阅者和发布者不是直接关联的。目标和观察者是直接联系在一起的。观察者把自身添加到了目标对象中,可见和发布订阅模式差别还是很大的。在这种模式下,目标更像一个发布者,他让添加进来的所有观察者都执行了传入的函数,而观察者就像一个订阅者。虽然两种模式都存在订阅者和发布者(具体观察者可认为是订阅者、具体目标可认为是发布者),但是观察者模式是由具体目标调度的,而发布/订阅模式是统一由调度中心调的,所以观察者模式的订阅者与发布者之间是存在依赖的,而发布/订阅模式则不会。

总结

虽然在学习这两种模式的时候有很多的坎坷,最终还是按照自己的理解写出来了两个案例。或许理解的有偏差,如果哪里有问题,希望大家在下面留言指正,我会尽快做出修复的。

感兴趣的朋友可以使用在线HTML/CSS/JavaScript代码运行工具:http://tools.jb51.net/code/HtmlJsRun测试上述代码运行效果。

更多关于JavaScript相关内容感兴趣的读者可查看本站专题:《javascript面向对象入门教程》、《JavaScript错误与调试技巧总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript遍历算法与技巧总结》及《JavaScript数学运算用法总结》

希望本文所述对大家JavaScript程序设计有所帮助。

(0)

相关推荐

  • 学习JavaScript设计模式之观察者模式

    一.定义 观察者模式(发布-订阅模式):其定义对象间一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 在JavaScript中,一般使用事件模型来替代传统的观察者模式. 好处: (1)可广泛应用于异步编程中,是一种替代传递回调函数的方案. (2)可取代对象之间硬编码的通知机制,一个对象不用再显示地调用另外一个对象的某个接口.两对象轻松解耦. 二.DOM事件–观察者模式典例 需要监控用户点击document.body的动作,但是我们没有办法预知用户将在什么时间点击

  • NodeJS设计模式总结【单例模式,适配器模式,装饰模式,观察者模式】

    本文实例讲述了NodeJS设计模式.分享给大家供大家参考,具体如下: 1 . 单例模式 顾名思义,单例就是保证一个类只有一个实例,实现的方法是,先判断实例是否存在,如果存在则直接返回,若不存在,则创建实例对象,并将实例对象保存在静态变量中,当下次请求时,则可以直接返回这个对象实例,这就确保了一个类只有一个实例对象.举个例子吧~一间学校刚刚起建还没有图书馆,有的同学就向领导提意见:"hey! 哥们,能不能帮我们建一个图书馆? "(想要一个图书馆实例),然后领导说:"no pro

  • JavaScript观察者模式(publish/subscribe)原理与实现方法

    本文实例讲述了JavaScript观察者模式(publish/subscribe)原理与实现方法.分享给大家供大家参考,具体如下: 观察者模式又叫做发布订阅模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察着对象.它是由两类对象组成,主题和观察者,主题负责发布事件,同时观察者通过订阅这些事件来观察该主体,发布者和订阅者是完全解耦的,彼此不知道对方的存在,两者仅仅共享一个自定义事件的名称. 在Nodejs中通过EventEmitte

  • JavaScript原生实现观察者模式的示例

    观察者模式又叫做发布订阅模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察着对象. 它是由两类对象组成,主题和观察者,主题负责发布事件,同时观察者通过订阅这些事件来观察该主体,发布者和订阅者是完全解耦的,彼此不知道对方的存在,两者仅仅共享一个自定义事件的名称. 在Nodejs中通过EventEmitter实现了原生的对于这一模式的支持. 在JavaScript中事件监听机制就可以理解为一种观察者模式.通过onclick进行事件绑定

  • js 发布订阅模式的实例讲解

    废话不多说,直接上代码 //发布订阅模式 class EventEmiter{ constructor(){ //维护一个对象 this._events={ } } on(eventName,callback){ if( this._events[eventName]){ //如果有就放一个新的 this._events[eventName].push(callback); }else{ //如果没有就创建一个数组 this._events[eventName]=[callback] } } e

  • JavaScript观察者模式(经典)

    Observer模式也叫观察者模式,是由GoF提出的23种软件设计模式的一种.Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态. Observer模式的概念 Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态. Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步. Observer模式的角色: Subject(被观察

  • 深入理解Javascript中的观察者模式

    简介 观察者模式又叫发布订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己.讲道理,如果我们写的不是稍微底层的代码,可能不会用到它. 但是有了它会让代码更灵活,更加规整,减少冗余代码,方便分模块,分功能开发. 使用观察者模式的好处: 支持简单的广播通信,自动通知所有已经订阅过的对象. 页面载入后目标对象很容易与观察者存在一种动态关联,增加了灵活性. 目标对

  • node.js 发布订阅模式的实例

    实例如下: //导入内置模块 let EventEmitter = require('events'); let util=require('util'); //Man继承EventEmitter util.inherits(Man,EventEmitter); //创建一个函数 function Man(){} //实例化函数 let man=new Man(); function findGirl() { console.log('找新的女朋友') } function saveMoney(

  • JS设计模式之观察者模式实现实时改变页面中金额数的方法

    本文实例讲述了JS设计模式之观察者模式实现实时改变页面中金额数的方法.分享给大家供大家参考,具体如下: 观察者设计模式概念: 有时被称作发布/订阅模式,观察者模式定义了一种一对多的依赖关系,让多个观察者(每个处的主账号金额函数)对象同时监听某一个主题对象(修改子账号金额后调用的deliver的对象Publisher).这个主题对象在状态(调用deliver方法)发生变化时,会通知所有观察者对象,使它们能够自动更新自己. 在一个会员管理系统中,主账号给子账号充值金额的功能. 场景:主账号有1000

  • JavaScript设计模式之观察者模式(发布订阅模式)原理与实现方法示例

    本文实例讲述了JavaScript设计模式之观察者模式(发布订阅模式)原理与实现方法.分享给大家供大家参考,具体如下: 观察者模式,又称为发布订阅模式,它定义了一种一对多的关系,让多个观察者对象同时监听某一个主题对象,这个主题对象的状态发生变化时就会通知所有的观察者对象,使得它们能够自动更新自己的状态. 在观察者模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知.订阅者也称为观察者,而被观察的对象称为发布者或主题.当发生了一个重要的事件时,发布

  • JavaScript设计模式之观察者模式(发布者-订阅者模式)

    观察者模式( 又叫发布者-订阅者模式 )应该是最常用的模式之一. 在很多语言里都得到大量应用. 包括我们平时接触的dom事件. 也是js和dom之间实现的一种观察者模式. 复制代码 代码如下: div.onclick  =  function click (){ alert ( "click' ) } 只要订阅了div的click事件. 当点击div的时候, function click就会被触发. 那么到底什么是观察者模式呢. 先看看生活中的观察者模式. 好莱坞有句名言. "不要给我

随机推荐