设计模式学习笔记-观察者模式/发布-订阅模式

本文探讨了观察者模式和发布订阅模式在前端开发中的应用,对比了两种模式的实现方式及特点。观察者模式中,Subject直接管理并通知Observers;而发布订阅模式通过MiddleWare中介实现Subject与Subscribers的解耦。

主要记录平时在设计模式以及前端代码组织上的一些思考,贴出来的代码只给出ES5的实现版本,后续会补上ES6的写法。(因为ES6多了proxy,所以两种实现方式还有很多可以比较的地方。)

观察者模式/发布-订阅模式

前端的学习中最重要的可能就是观察者模式和发布订阅模式了。传统的事件处理和现代的mvvm框架,基本都是基于这两种设计模式构建的。
我网上查的资料很多都是把这两种设计模式放一块谈,这两种设计模式有联系也有区别,下面两段话是维基百科对这两种模式的解释。抛开网上各种版本的实现不谈,后面,我想通过一些简单的demo谈谈我对这两种设计模式的理解:

观察者模式是软件设计模式的一种。在此种模式中,一个目标对象管理所有相依于它的观察者对象,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实时事件处理系统。

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

观察者模式

按照上面关于观察者模式的概念,我们大概能抽象出这样的概念。

观察者模式是被观察者(Subject)自己管理观察者(Observers)。当Subject状态改变时,通知所有的Observers。我们可以通过以下代码来实现这样的一个功能(ES5):

function Subject (name) {
  var name = name
  this.observers = []
  Object.defineProperty(this, 'name', {
    enumerable: true,
    configurable: true,
    get: function () {
      return name
    },
    set: function (newName) {
      name = newName
      this.notify(newName)
    }
  })
}
Subject.prototype.addObserver = function (observer) {
  return this.observers.push(observer)
}
Subject.prototype.removeObserver = function (id) {
  return this.observers[id] = null
}
Subject.prototype.cleanObservers = function () {
  return this.observers = []
}
Subject.prototype.notify = function (name) {
  this.observers.forEach(item => {
    if (typeof item === 'function') {
      item.call(this, name)
    }
  })
}
复制代码

这个代码很简单,主要是提供观察Subject对象的name属性的变化。对象本身提供添加和删除Observer的功能。当name发生改变时,通过notify方法通知所有的Observer。运行效果如下:

var sub = new Subject('哆啦A梦')

sub.addObserver(function (name) {
  console.log('observer exec: ' + name)
})

sub.name = '大雄'
// observer exec: 大雄
复制代码

从运行结果我们可以知道,当name发生改变的时候,就会通知到Observer

发布-订阅模式

个人觉得发布订阅模式是对观察者模式的一种优化。

发布订阅模式主体(Subject)并不是直接通知Observer,而是通知一个中间件(MiddleWare),再由中间件进行筛选分发给一个个订阅者(Subscriber)。发布者与订阅者松耦合,甚至不需要知道它们的存在。

这中间最主要的是实现这个MiddleWare。暂时不考虑分发的功能,我们大概知道MiddleWare至少需要有添加订阅者、移除和清空订阅者这三个最基本的功能。整体实现,代码大致如下:

// 中间件
function MiddleWare (target) {
  this.target = target
  this.subscribers = []
}
MiddleWare.prototype.addSubscriber = function (target, subscriber) {
  subscriber = subscriber || target
  return this.subscribers.push(subscriber)
}
MiddleWare.prototype.removeSubscriber = function (id) {
  return this.subscribers[id] = null
}
MiddleWare.prototype.cleanSubscriber = function () {
  return this.subscribers = []
}
MiddleWare.prototype.notify = function (name) {
  this.subscribers.forEach(item => {
    if (typeof item === 'function') {
      item.call(this.target, name)
    }
  })
}
// 发布对象的某个属性
// 返回的是一个中间件
function doPublish (obj, key, val) {
  var innerVal = val
  var middleWare = new MiddleWare(obj)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function () {
      return innerVal
    },
    set: function (newValue) {
      innerVal = newValue
      middleWare.notify(newValue)
    }
  })
  return middleWare
}
复制代码

对于原本的Subject我们可以改写为一个普通的对象即可:

function Subject (name) {
  this.name = name
}
复制代码

通过以下代码来看看最终的运行效果

// 创建实例
var sub = new Subject('多啦A梦')

// 发布
var middleWare = doPublish(sub, 'name', sub.name)

// 订阅
middleWare.addSubscriber(function (name) {
  console.log(this)
  console.log('subscriber exec: ' + name)
})

sub.name = '大雄'
// subscriber exec: 大雄
复制代码

至于如何分发消息给订阅者,我们可以在MiddleWarenotify方法里面进行分发。这里也就不做实现了。

看起来发布-订阅模式好像代码量变多了,而且实现的需求也大致相同,但是实际上两种设计模式有本质上的区别:观察者模式实际上是由Subject主动推送的,它本身提供关于Observer的注册和通知的方法。但是发布-订阅模式则是由外部代码去监听Subject的变化。两者的应用场景不一样。

转载于:https://juejin.im/post/5bd94c8ae51d4568471d7a4e

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值