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

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

观察者模式

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

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

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

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

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

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

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

15fe1b1f1797e09a

图片源于网络,侵权必删函数

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

//  观察者队列
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的订阅者。固然也有多是订阅者本身过来拉取,看具体实现。

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

15fe1b1f07c13719

图片源于网络,侵权必删

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

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

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. 观察者模式须要在单个应用程序地址空间中实现。另外一方面,发布者/订阅者模式更像是跨应用程序模式。

810680-20181110141219325-989417119.png

图片源于网络,侵权必删

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

总结

虽然在学习这两种模式的时候有不少的坎坷,最终仍是按照本身的理解写出来了两个案例。或许理解的有误差,若是哪里有问题,但愿你们在下面留言指正,我会尽快作出修复的。

若是你和我同样喜欢前端的话,能够加Qq群:135170291,期待你们的加入。

相关文章
相关标签/搜索