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

特点:这种模式一般会定义一个主体和众多的个体,这里主体可以想象为一个消息中心,里面有各种各样的消息,众多的个体可以订阅不同的消息,当未来消息中心发布某条消息的时候,订阅过它的个体就会得到通知。

1、定义消息中心

首先,我们定义一个消息中心,里面包含存储消息的_msg对象,返回一个对象,里面有3个方法属性分别用于订阅一个消息,发布消息和取消订阅的消息。

// 消息中心
const msgCenter = (function() {
  let _msg = {} // 存储消息

  return {
    // 用于订阅一个消息,type表示什么类型的消息,fn表示当消息中心发布这条消息的时候执行的回调函数
    register: function (type, fn) { },
    // 用于发布消息,type表示消息类型,当消息中心有很多消息类型存在,args表示发布这条消息附带的信息
    fire: function (type, args) { },
    // 用于取消订阅的消息,type表示消息类型,fn表示要取消这个类型下面的哪一个回调函数
    cancel: function (type, fn) { }
  }
})()

一个简单的观察者模式的框架就搭建出来了,我们最重要的工作就是实现这3个方法,

2、定义个体的构造函数和个体的方法

// 个体构造函数
function Person () {
  // 存储的是个体所订阅过的消息类型
  this.alreadyRegister = {}
}
// 每个个体都有的一些方法,放到原型链上,订阅消息
Person.prototype.register = function (type, fn) {
  // this.alreadyRegister[type]存在说明之前已经订阅过了
  if (this.alreadyRegister[type]) {
    console.log('您已经订阅过该消息,请不要重复订阅!')
  } else {
    msgCenter.register(type, fn)
    // 在个体内再另存一份
    this.alreadyRegister[type] = fn
  }
}
// 取消订阅
Person.prototype.cancel = function (type) {
  msgCenter.cancel(type, this.alreadyRegister[type])
  delete this.alreadyRegister[type]
}

在订阅消息时,要保证消息中心有一份信息,个体里面也会有一份备份

3、实现效果

let person1 = new Person()
let person2 = new Person()
let person3 = new Person()

person1.register('playInfo', function (e) {
  console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person1.register('workInfo', function (e) {
  console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person2.register('playInfo', function (e) {
  console.log(`person2得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('playInfo', function (e) {
  console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('workInfo', function (e) {
  console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})

msgCenter.fire('playInfo', {info: '刘亦菲女神!'})
msgCenter.fire('workInfo', {info: '互联网冰河世纪!'})

想要实现的效果是:当消息中心发布某个类型的消息,订阅了该类型的个体就会得到该消息(收到通知),并且,消息要传到个体的register方法的fn参数中。

4、实现消息中心的方法

// 消息中心
const msgCenter = (function () {
  let _msg = {} // 存储消息

  return {
    // 用于订阅一个消息,type表示什么类型的消息,fn表示当消息中心发布这条消息的时候执行的回调函数
    register: function (type, fn) {
      // 目的让_msg变成这样的结构{ 'playInfo':[person1.alreadyRegister.playInfo,person2.alreadyRegister.playInfo...] },这样就用数组存储了哪些个体订阅了该类型的消息
      // 因为数组里存放着所有订阅者的回调函数,换句话说根本不用管哪些个体订阅了,一旦发布了该类型的新消息,直接无脑执行数组里所有的回调函数即可让所有订阅的个体收到新消息
      if (_msg[type]) {
        _msg[type].push(fn)
      } else {
        _msg[type] = [fn]
      }
    },
    // 用于发布消息,type表示消息类型,当消息中心有很多消息类型存在,args表示发布这条消息附带的信息
    fire: function (type, args) {
      // 如果type不存在说明压根就没有这个消息,更没有人订阅(也就是没有数组和fn),没人订阅你发布给谁看呢?直接return不做处理
      if (!_msg[type]) {
        return
      }
      const event = {
        type: type,
        args: args || {}
      }
      for (let i = 0;i < _msg[type].length;i++) {
        // 把新消息传递给所有订阅者的回调函数fn,然后调用函数
        _msg[type][i](event)
      }
    },
    // 用于取消订阅的消息,type表示消息类型,fn表示要取消这个类型下面的哪一个回调函数
    cancel: function (type, fn) {
      if (!_msg[type]) {
        return
      }
      // 遍历,找到数组中的fn,然后删除,这样就实现了取消订阅的功能
      for (let i = 0;i < _msg[type].length;i++) {
        if (_msg[type][i] === fn) {
          _msg[type].splice(i, 1)
          break
        }
      }
    }
  }
})()

5、验证

在这里插入图片描述
下面再验证一下重复订阅以及取消订阅:

// 重复订阅
person1.register('playInfo', function (e) {
  console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
console.log('————————————')
person1.cancel('playInfo')
msgCenter.fire('playInfo', {info: '胡歌男神!'})

在这里插入图片描述

完整代码

// 消息中心
const msgCenter = (function () {
  let _msg = {} // 存储消息

  return {
    // 用于订阅一个消息,type表示什么类型的消息,fn表示当消息中心发布这条消息的时候执行的回调函数
    register: function (type, fn) {
      // 目的让_msg变成这样的结构{ 'playInfo':[person1.alreadyRegister.playInfo,person2.alreadyRegister.playInfo...] },这样就用数组存储了哪些个体订阅了该类型的消息
      // 因为数组里存放着所有订阅者的回调函数,换句话说根本不用管哪些个体订阅了,一旦发布了该类型的新消息,直接无脑执行数组里所有的回调函数即可让所有订阅的个体收到新消息
      if (_msg[type]) {
        _msg[type].push(fn)
      } else {
        _msg[type] = [fn]
      }
    },
    // 用于发布消息,type表示消息类型,当消息中心有很多消息类型存在,args表示发布这条消息附带的信息
    fire: function (type, args) {
      // 如果type不存在说明压根就没有这个消息,更没有人订阅(也就是没有数组和fn),没人订阅你发布给谁看呢?直接return不做处理
      if (!_msg[type]) {
        return
      }
      const event = {
        type: type,
        args: args || {}
      }
      for (let i = 0;i < _msg[type].length;i++) {
        // 把新消息传递给所有订阅者的回调函数fn,然后调用函数
        _msg[type][i](event)
      }
    },
    // 用于取消订阅的消息,type表示消息类型,fn表示要取消这个类型下面的哪一个回调函数
    cancel: function (type, fn) {
      if (!_msg[type]) {
        return
      }
      // 遍历,找到数组中的fn,然后删除,这样就实现了取消订阅的功能
      for (let i = 0;i < _msg[type].length;i++) {
        if (_msg[type][i] === fn) {
          _msg[type].splice(i, 1)
          break
        }
      }
    }
  }
})()
// 个体构造函数
function Person () {
  // 存储的是个体所订阅过的消息类型
  this.alreadyRegister = {}
}
// 每个个体都有的一些方法,放到原型链上,订阅消息
Person.prototype.register = function (type, fn) {
  // this.alreadyRegister[type]存在说明之前已经订阅过了
  if (this.alreadyRegister[type]) {
    console.log('您已经订阅过该消息,请不要重复订阅!')
  } else {
    msgCenter.register(type, fn)
    // 在个体内再另存一份
    this.alreadyRegister[type] = fn
  }
}
// 取消订阅
Person.prototype.cancel = function (type) {
  msgCenter.cancel(type, this.alreadyRegister[type])
  delete this.alreadyRegister[type]
}

let person1 = new Person()
let person2 = new Person()
let person3 = new Person()

person1.register('playInfo', function (e) {
  console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person1.register('workInfo', function (e) {
  console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person2.register('playInfo', function (e) {
  console.log(`person2得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('playInfo', function (e) {
  console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
person3.register('workInfo', function (e) {
  console.log(`person3得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})

msgCenter.fire('playInfo', {info: '刘亦菲女神!'})
msgCenter.fire('workInfo', {info: '互联网冰河世纪!'})
// 重复订阅
person1.register('playInfo', function (e) {
  console.log(`person1得到了关于${e.type}的消息,消息内容是:${e.args.info}`)
})
console.log('————————————')
person1.cancel('playInfo')
msgCenter.fire('playInfo', {info: '胡歌男神!'})
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端吕小布

您的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值