观察者模式于发布订阅模式的关系

观察者模式于发布订阅模式的关系

在《JavaScript设计模式》中,对观察者模式的定义:又被称作发布-订阅者模式或消息机制,定义了一中依赖关系,解决了主题于观察者之间功能的耦合。

这里的定义将观察者和发布-订阅者,两种模式称作同一中,但是实际上观察者模式和发布订阅模式还是有一些区别的。

下面是一段书中作为观察者模式的代码:

NoDatavar Observer = (function () {
   let _messages = {}
   return {
       regist: function(type, fn) {
           if (typeof _messages[type] === 'undefined') {
               _messages[type] = [fn]
           }else {
               _messages[type].push(fn)
           }
       },
       fire: function(type, args) {
           if (!_messages[type]) { 
               return 
           }
           let events = {
               type: type,
               args: args || {}
           }
           for (let i = 0; i < _messages[type].length; i++) {
               _messages[type][i].call(this, events)
           }
       },
   	   remove: function(type, fn) {
           if (_messages[type] instanceof Array) {
               for (let i = _messages[type].length - 1; i >= 0; i--) {
                   _messages[type][i] === fn && _messages[type].splice(i, 1)
               }
           }
       }
   }
})()

// 订阅者
// 学生类
const Student = function(result) {
    let that = this
    that.result = result
    that.say = function() {
        console.log(that.result)
    }
}

Student.prototype.answer = function(question) {
    Observer.regist(question, this.say)
}

Student.prototype.sleep = function(question) {
    console.log(this.result + ':' + question + '已被注销')
    Observer.remove(question, this.say)
}

// 发布者
// 教师类
const Teacher = function() {}
Teacher.prototype.ask = function(question) {
    console.log('问题是:' + question)
    Observer.fire(question)
}

const student1 = new Student('学生1回答问题')
const student2 = new Student('学生2回答问题')
const student3 = new Student('学生3回答问题')

student1.answer('什么是设计模式')
student1.answer('什么是观察者模式')
student2.answer('什么是设计模式')
student3.answer('什么是设计模式')
student3.answer('什么是观察者模式')


const teacher = new Teacher()
teacher.ask('什么是设计模式')
student3.sleep('什么是观察者模式')
teacher.ask('什么是观察者模式')

//问题是:什么是设计模式
//学生1回答问题
//学生2回答问题
//学生3回答问题
//学生3回答问题:什么是观察者模式已被注销
//问题是:什么是观察者模式
//学生1回答问题

  但实际上这段代码使用的应该是发布订阅模式。接下来详细了解一下二者的关系。

一、观察者模式

  在对象之间定义一个一对多的依赖,当对象自身状态改变的时候,会自动通知给关心该状态的观察者。解决了主体对象与观察者之间功能的耦合,即一个对象的状态改变给其他对象通知的问题。

下面是一个简单的观察者模式例子:

// 被观察者
class Subject {
    constructor() {
        // 观察者列表
        this.observers = []
    }
    // 添加
    add(observer) {
        this.observers.push(observer)
    }
    // 删除
    remove(observer) {
        let index = this.observers.indexOf(observer)
        if (index > -1) {
            this.observers.splice(index, 1)
        }
    }
    //	通知
    notify() {
        for (let observer of this.observers) {
            observer.update()
        }
    }
}

// 观察者类
class Observer {
    constructor(name) {
        this.name = name
    }
    // 观察目标更新时
    update() {
        console.log(`${this.name},项目又有新bug了`)
    }
}

let subject = new Subject()

let obs1 = new Observer('前端开发')
let obs2 = new Observer('后端开发')

subject.add(obs1)
subject.add(obs2)

subject.notcify()

// 前端开发,项目又有新bug了
// 后端开发,项目又有新bug了

  在以上代码中可以很明显的看出有两个类,一个是Subject是被观察者类,构造函数中有一个用来存放观察者的列表,当被观察者发生状态改变时,通过遍历被观察者列表来通知观察者。添加和删除方法用来添加和删除观察者,notify用来通知观察者。通过创建实例,调用被观察者的notify方法来模拟被观察者状态改变时的事件调用。实现了简单的观察者模式。

二、发布订阅模式

  也是定义一对多的依赖关系,对象状态改变后,通知给所有关心这个状态的订阅者。发布订阅模式有订阅的动作,可以不和观察者直接产生联系,只要能订阅上关心的状态即可,通常可用第三媒介来做,而发布者也会利用第三媒介来通知订阅者。

以下是一个发布订阅者示例:

class PubSub {
    constructor() {
        this.list = {}
    }
    subscribe(key, fn) {
        if (!this.list[key]) {
            this.list[key] = []
        }
        this.list[key].push(fn)
    }
    publish(key, ...arg) {
        for (let fn of this.list[key]) {
            fn.call(this, ...arg)
        }
    }
    unSubscribe(key, fn) {
        let fnList = this.list[key]
        if (!fnList) {
            return false
        }
        if (!fn) {
            fnList && (fnList.length = 0)
        } else {
            let index = fnList.indexOf(fn)
            fnList.splice(index, 1)
        }
    }
}

const pubSub = new PubSub()

pubSub.subscribe('onwork', time => {
    console.log(`上班时间:${time}`)
})
pubSub.subscribe('offwork', time => {
    console.log(`下班时间:${time}`)
})
pubSub.subscribe('launch', time => {
    console.log(`午饭时间:${time}`)
})

pubSub.publish('onwork', '8:30')
pubSub.publish('launch', '12:00')
pubSub.publish('offwork', '17:30')

// 上班时间:8:30
// 午饭时间:12:00
// 下班时间:17:30

  上面的代码就是发布订阅最重要的部分,虽然没有明显的实现观察者类和被观察者类,但观察者和被观察者在发布订阅模式中本来就不会有直接关联,都只需调用以上代码(事件总线)中的发布和订阅方法即可。这样的代码可以被提取出来,可以被重复使用,可以被多个观察者和被观察者使用。

三、两者之间的异同

1、从概念上没什么不同,都是在解决对象之间解耦的问题,通过事件的方式在某个时间点进行触发,监听这个事件的订阅者可以进行相应的操作。

2、从实现上来说,观察者模式对订阅事件的订阅者通过发布自身来维护,后续的一系列操作都需要通过发布者完成;发布订阅模式是订阅者和发布者有一个事件总线,操作都要经过事件总线完成。

3、在观察者模式中,观察者知道被观察的主体,主体也维护观察者的记录。在发布订阅模式中,发布者和订阅者不需要知道彼此,他们通过事件总线来实现通信。观察者模式进一步抽象,能抽出的公共代码就是事件总线。

4、观察者模式是松耦合的,发布订阅模式,完全不耦合。

5、观察者大多数主要以同步方式实现,发布订阅模式大多数是异步模式。

6、观察者模式基本用于单个应用内部,发布订阅模式更多的是跨应用的模式

Vue2中通过拦截数据进行依赖收集,等待数据变更时,通知依赖的Watcher进行组件更新。

  通过使用Object.defineReactive()方法,对数据对象添加get()方法进行拦截,收集依赖,在通过添加set()方法,对修改数据进行拦截,通知依赖更新,从而达到数据的响应式。在此过程中也使用了发布订阅的模式,事件总线就是用来收集和通知更新依赖的Dep()函数。

// src/observer/dep.js

export default class Dep{
  static target: ?Watcher;
  id: number;
  subs: Array<Watcher>;
  constructor () {
    this.id = uid++
    this.subs = []
  }

  addSub(sub) {
    this.subs.push(sub)
  }

  removeSub(sub) {
    romove(this.subs, sub)
  }

  depend() {
    if (Dep.target) {
      Dep.target.addDep(this)
    }
  }

  notify() {
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}

  

posted @ 2021-10-19 19:05  leayun  阅读(112)  评论(0)    收藏  举报