一、定义

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

在这里先讲一下面向对象设计的一个重要原则——单一职责原则。因此系统的每个对象应该将重点放在问题域中的离散抽象上。因此理想的情况下,一个对象只做一件事情。这样在开发中也就带来了诸多的好处:提供了重用性和维护性,也是进行重构的良好的基础。几乎所有的设计模式都是基于这个基本的设计原则来的。

观察者模式(又被称为发布-订阅(Publish/Subscribe)模式。说到发布订阅,最熟悉的就是我们的微信公众号了,那就用这个来举例子:

观察者模式的简单实现

假设微信用户就是观察者,微信公众号是被观察者,有多个的微信用户关注了程序猿这个公众号,当这个公众号更新时就会通知这些订阅的微信用户。

先实现微信公众号的类

  1. /* 所有公众号 */
  2. class Pubsub {
  3. /*
  4. follows 保存公众号下的用户和用户的操作,数据结构如下:
  5. {
  6. ["Github最新开源项目"]: [ {id: 0, fn: fn} , {id: 1, fn: fn} , {fn: fn} ],
  7. ["CNode社区新闻"]: [ {id: 0, fn: fn} , {id: 1, fn: fn} , {fn: fn} ],
  8. }
  9. * */
  10. constructor (){
  11. this.follows = {}
  12. this.id = -1
  13. }
  14. /* 订阅方法
  15. * @param {string} userName 公众号名
  16. * @param {function} fn 公众号发布文章后用户会采取的操作
  17. * @return {string} id 每个用户在公众号中的唯一标识
  18. * */
  19. subscrilb(pubsubName, fn) {
  20. this.follows[pubsubName] || (this.follows[pubsubName] = [])
  21. let id = '' + (++this.id)
  22. this.follows[pubsubName].push({ id, fn })
  23. return id
  24. }
  25. /* 发布方法
  26. * @param {string} userName 公众号名
  27. * */
  28. publish (pubsubName) {
  29. let len = this.follows[pubsubName].length;
  30. for (let i = 0; i < len; i++) {
  31. console.log(this.follows)
  32. this.follows[pubsubName][i].fn()
  33. }
  34. }
  35. /* 取消订阅方法
  36. * @param {string} userName 公众号名
  37. * @return {string} id 每个用户在公众号中的唯一标识
  38. * */
  39. unsubscribe(pubsubName, id) {
  40. for (let key of this.follows) {
  41. if(key == pubsubName) {
  42. for (let i = 0,len = this.follows[pubsubName].length; i< len; i++) {
  43. if (this.follows[pubsubName][i].id === id) {
  44. this.follows[pubsubName].splice(i, 1)
  45. }
  46. }
  47. }
  48. }
  49. }
  50. }
  51. let pubsub = new Pubsub();

接下来设置用户类

  1. class User {
  2. constructor(name){
  3. this.name = name
  4. }
  5. /*
  6. * 用户订阅方法
  7. **/
  8. follow(pubsubName, fn) {
  9. pubsub.subscrilb(pubsubName, fn)
  10. }
  11. }
  12. let user1 = new User('user-1')
  13. let user2 = new User('user-2')

然后进行订阅发布

  1. user1.follow('CNode社区新闻',function() {
  2. console.log('user 1 关注此社区!')
  3. })
  4. user2.follow('CNode社区新闻',function() {
  5. console.log('user 2 关注此社区!')
  6. })
  7. pubsub.publish('CNode社区新闻')
  8. user1.follow('Github最新咨询',function() {
  9. console.log('user 1 关注此社区!')
  10. })
  11. user2.follow('Github最新咨询',function() {
  12. console.log('user 2 关注此社区!')
  13. })
  14. pubsub.publish('Github最新咨询')

观察者模式优缺点

优点:

  • 我们作为订阅者不必每次都去查看这个公众号有没有新文章发布, 公众号作为发布者会在合适时间通知我们
  • 我们与公众号之间不再强耦合在一起。公众号不关心谁订阅了它,
    不管你是男是女还是宠物狗,它只需要定时向所有订阅者发布消息即可
  • 可以广泛应用于异步编程,它可以代替我们传统的回调函数
    我们不需要关注对象在异步执行阶段的内部状态,我们只关心事件完成的时间点

缺点:

  • 在应用观察者模式时需要考虑一下开发效率和运行效率的问题,程序中包括一个被观察者、多个观察者,开发、调试等内容会比较复杂
  • 由于JavaScript单线程异步机制,即使一个观察者卡顿了,也不会影响整体的执行效率。(多线程同步便会阻塞)

总结

观察者模式有两个明显的优点

  • 时间上解耦
  • 对象上解耦

关于观察者模式,在浏览器和Node都有良好的事件机制支持,不必自己实现,本文只是简单了解。