资讯专栏INFORMATION COLUMN

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

tabalt / 1274人阅读

摘要:观察者模式与发布订阅模式观察者模式概念一个被观察者的对象,通过注册的方式维护一组观察者对象。当被观察者发生变化,就会产生一个通知,通过广播的方式发送出去,最后调用每个观察者的更新方法。

观察者模式与发布/订阅模式 观察者模式 概念

一个被观察者的对象,通过注册的方式维护一组观察者对象。当被观察者发生变化,就会产生一个通知,通过广播的方式发送出去,最后调用每个观察者的更新方法。当观察者不再需要接受被观察者的通知时,被观察者可以将该观察者从所维护的组中删除。

实现

这个实现包含以下组件:

被观察者:维护一组观察者, 提供用于增加和移除观察者的方法

观察者:提供一个更新接口,用于当被观察者状态变化时,得到通知

具体的被观察者:状态变化时广播通知给观察者,保持具体的观察者的信息

具体的观察者:保持一个指向具体被观察者的引用,实现一个更新接口,用于观察,以便保证自身状态总是和被观察者状态一致的

首先,对被观察者维护的一组观察者(列表)进行建模

function ObserverList() {
  this.observerList = []
}

ObserverList.prototype.add = function(obj) {
  return this.observerList.push(obj)
}

ObserverList.prototype.Empty = function() {
  this.observerList = []
}

ObserverList.prototype.removeAt = function(index) {
  this.observerList.splice(index, 1)
}

ObserverList.prototype.count = function() {
  return this.observerList.length
}

ObserverList.prototype.get = function(index) {
  if (index > -1 && index < this.observerList.length) {
    return this.observerList[index]
  }
}

// Extend an object with an extension
function extend(extension, obj) {
  for (var key in extension) {
    obj[key] = extension[key]
  }
}

接着,对被观察者以及其增加、删除、通知能力进行建模

function Subject() {
  this.observers = new ObserverList()
}

Subject.prototype.addObserver = function(observer) {
  this.observers.add(observer)
}

Subject.prototype.removeObserver = function(observer) {
  this.observers.removeAt(this.observers.IndexOf(observer, 0))
}

Subject.prototype.notify = function(context) {
  var observerCount = this.observers.count()

  for (var i = 0; i < observerCount; i++) {
    this.observers.get(i).update(context)
  }
}

接着,对观察者进行建模,这里的 update 函数之后会被具体的行为覆盖

function Observer() {
  this.update = function() {
    // ...
  }
}

样例应用

我们使用上面的观察者组件,现在我们定义

一个按钮,这个按钮用于增加新的充当观察者的选择框到页面上

一个控制用的选择框 , 充当一个被观察者,通知其它选择框是否应该被选中

一个容器,用于放置新的选择框



// DOM 元素的引用
var controlCheckbox = document.getElementById("mainCheckbox"),
  addBtn = document.getElementById("addNewObserver"),
  container = document.getElementById("observersContainer")

// 具体的被观察者

// Subject 类扩展 controlCheckbox
extend(new Subject(), controlCheckbox)

//点击 checkbox 将会触发对观察者的通知
controlCheckbox["onclick"] = new Function(
  "controlCheckbox.notify(controlCheckbox.checked)"
)

addBtn["onclick"] = AddNewObserver

// 具体的观察者

function AddNewObserver() {
  // 建立一个新的用于增加的 checkbox
  var check = document.createElement("input")
  check.type = "checkbox"

  // 使用 Observer 类扩展 checkbox
  extend(new Observer(), check)

  // 使用定制的 update 函数重载
  check.update = function(value) {
    this.checked = value
  }

  // 增加新的观察者到我们主要的被观察者的观察者列表中
  controlCheckbox.AddObserver(check)

  // 将元素添加到容器的最后
  container.appendChild(check)
}

上述示例中

Subject 类扩展 controlCheckbox,所以 controlCheckbox 是具体的被观察者

点击 addBtn 时,生成一个新的 check,check 被 Observer 类所拓展并重写了 update 方法,所以 check 是具体的观察者,最后 controlCheckbox 将 check 添加到了 controlCheckbox 所维护的观察者列表中

点击 controlCheckbox,调用了被观察者的 notify 方法,进而触发了 controlCheckbox 中所维护的观察者的 update 方法

发布/订阅模式 实现
var pubsub = {}

;(function(q) {
  var topics = {},
    subUid = -1

  // Publish or broadcast events of interest
  // with a specific topic name and arguments
  // such as the data to pass along
  q.publish = function(topic, args) {
    if (!topics[topic]) {
      return false
    }

    var subscribers = topics[topic],
      len = subscribers ? subscribers.length : 0

    while (len--) {
      subscribers[len].func(topic, args)
    }

    return this
  }

  // Subscribe to events of interest
  // with a specific topic name and a
  // callback function, to be executed
  // when the topic/event is observed
  q.subscribe = function(topic, func) {
    if (!topics[topic]) {
      topics[topic] = []
    }

    var token = (++subUid).toString()
    topics[topic].push({
      token: token,
      func: func
    })
    return token
  }

  // Unsubscribe from a specific
  // topic, based on a tokenized reference
  // to the subscription
  q.unsubscribe = function(token) {
    for (var m in topics) {
      if (topics[m]) {
        for (var i = 0, j = topics[m].length; i < j; i++) {
          if (topics[m][i].token === token) {
            topics[m].splice(i, 1)
            return token
          }
        }
      }
    }
    return this
  }
})(pubsub)
样例应用 1
// Another simple message handler

// A simple message logger that logs any topics and data received through our
// subscriber
var messageLogger = function(topics, data) {
  console.log("Logging: " + topics + ": " + data)
}

// Subscribers listen for topics they have subscribed to and
// invoke a callback function (e.g messageLogger) once a new
// notification is broadcast on that topic
var subscription = pubsub.subscribe("inbox/newMessage", messageLogger)

// Publishers are in charge of publishing topics or notifications of
// interest to the application. e.g:

pubsub.publish("inbox/newMessage", "hello world!")

// or
pubsub.publish("inbox/newMessage", ["test", "a", "b", "c"])

// or
pubsub.publish("inbox/newMessage", {
  sender: "hello@google.com",
  body: "Hey again!"
})

// We cab also unsubscribe if we no longer wish for our subscribers
// to be notified
// pubsub.unsubscribe( subscription );

// Once unsubscribed, this for example won"t result in our
// messageLogger being executed as the subscriber is
// no longer listening
pubsub.publish("inbox/newMessage", "Hello! are you still there?")
样例应用 2

旧的代码

$.ajax("http:// xxx.com?login", function(data) {
  header.setAvatar(data.avatar) // 设置 header 模块的头像
  nav.setAvatar(data.avatar) // 设置导航模块的头像
})

使用了发布/订阅模式的代码

$.ajax("http:// xxx.com?login", function(data) {
  pubsub.publish("loginSucc", data) // 发布登录成功的消息
})

// header 模块
var header = (function() {
  pubsub.subscribe("loginSucc", function(data) {
    header.setAvatar(data.avatar)
  })

  return {
    setAvatar: function(data) {
      console.log("设置 header 模块的头像")
    }
  }
})()

// nav 模块
var nav = (function() {
  pubsub.subscribe("loginSucc", function(data) {
    nav.setAvatar(data.avatar)
  })

  return {
    setAvatar: function(avatar) {
      console.log("设置 nav 模块的头像")
    }
  }
})()
优点

可以应用于异步编程中,比如 ajax 请求的 succ、error 等事件中,或者动画的每一帧完成之后去发布一个事件,从而不需要过多关注对象再异步运行期间的内部状态

取代对象之间硬编码的通知机制,一个对象不再显示地调用另外一个对象的某个接口,让这两个对象松耦合的联系在一起

缺点

创建订阅者需消耗一定内存,当你订阅一个消息后,即使消息直到最后都未发生,但这个订阅者也会始终存在于内存中

发布/订阅模式弱化对象之间的联系,对象和对象之间的必要联系也被深埋在背后,导致程序难以跟踪维护和理解

二者的不同

观察者模式要求想要接受相关通知的观察者必须到发起这个事件的被观察者上注册这个事件

controlCheckbox.AddObserver(check)

发布/订阅模式使用一个主题/事件频道,这个频道处于订阅者和发布者之间,这个事件系统允许代码定义应用相关的事件,避免订阅者和发布者之间的依赖性

pubsub.subscribe("inbox/newMessage", messageLogger)
pubsub.publish("inbox/newMessage", "hello world!")

参考资料

《JavaScript设计模式》 作者:Addy Osmani

《JavaScript设计模式与开发实践》 作者:曾探

设计模式(三):观察者模式与发布/订阅模式区别

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109522.html

相关文章

  • 设计模式察者模式发布订阅模式

    摘要:观察者模式与发布订阅的区别在模式中,知道,同时还保留了的记录。发布者订阅者在大多情况下是异步方式使用消息队列。图片源于网络侵权必删如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的。 学习了一段时间设计模式,当学到观察者模式和发布订阅模式的时候遇到了很大的问题,这两个模式有点类似,有点傻傻分不清楚,博客起因如此,开始对观察者和发布...

    BaronZhang 评论0 收藏0
  • 发布订阅模式实现及发布订阅模式察者模式的不同

    摘要:发布订阅者模式中,订阅者是不知道也不关心事件是为什么触发,是由哪一个事件触发,只知道事件触发时候,会告诉自己。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。 概念 发布订阅者模式,是javascript甚至大多数语言都有的语言模式,比较概念的解释是, 订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布...

    YanceyOfficial 评论0 收藏0
  • 发布订阅模式实现及发布订阅模式察者模式的不同

    摘要:发布订阅者模式中,订阅者是不知道也不关心事件是为什么触发,是由哪一个事件触发,只知道事件触发时候,会告诉自己。然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。在发布订阅模式中,组件是松散耦合的,正好和观察者模式相反。 概念 发布订阅者模式,是javascript甚至大多数语言都有的语言模式,比较概念的解释是, 订阅者把自己想订阅的事件注册到调度中心,当该事件触发时候,发布者发布...

    Jensen 评论0 收藏0
  • JavaScript 设计模式(六):察者模式发布订阅模式

    摘要:观察者模式维护单一事件对应多个依赖该事件的对象关系发布订阅维护多个事件主题及依赖各事件主题的对象之间的关系观察者模式是目标对象直接触发通知全部通知,观察对象被迫接收通知。 观察者模式(Observer) 观察者模式:定义了对象间一种一对多的依赖关系,当目标对象 Subject 的状态发生改变时,所有依赖它的对象 Observer 都会得到通知。 简单点:女神有男朋友了,朋友圈晒个图,甜...

    bingo 评论0 收藏0
  • 察者模式发布-订阅模式

    摘要:观察者模式观察者模式一个对象主体根据它维护的一个对象列表观察者,自动通知它们状态的任何变化。观察者模式是由具体目标直接调度的操作而发布订阅模式是在调度中心调度,发布者与订阅者不产生依赖。 观察者模式(Observer) 观察者模式:一个对象(主体)根据它维护的一个对象列表(观察者),自动通知它们状态的任何变化。(举例说明,电商平台关注(订阅)一家店铺(发布者)的鞋子,当鞋子上架之后店铺...

    刘东 评论0 收藏0

发表评论

0条评论

tabalt

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<