JavaScript设计模式与开发实践 - 观察者模式
概述
观察者模式又叫发布 - 订阅模式(Publish/Subscribe),它定义了一种一对多的关系,让多个观察者对象同时监听某一个目标对象(为了方便理解,以下将观察者对象叫做订阅者,将目标对象叫做发布者)。发布者的状态发生变化时就会通知所有的订阅者,使得它们能够自动更新自己。
观察者模式的使用场合就是:当一个对象的改变需要同时改变其它对象,并且它不知道具体有多少对象需要改变的时候,就应该考虑使用观察者模式。
观察者模式的中心思想就是促进松散耦合,一为时间上的解耦,二为对象之间的解耦。让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响到另一边的变化。
实现
(function (window, undefined) {
var _subscribe = null,
_publish = null,
_unsubscribe = null,
_shift = Array.prototype.shift, // 删除数组的第一个 元素,并返回这个元素
_unshift = Array.prototype.unshift, // 在数组的开头添加一个或者多个元素,并返回数组新的length值
namespaceCache = {},
_create = null,
each = function (ary, fn) {
var ret = null;
for (var i = 0, len = ary.length; i < len; i++) {
var n = ary[i];
ret = fn.call(n, i, n);
}
return ret;
};
// 订阅消息
_subscribe = function (key, fn, cache) {
if (!cache[key]) {
cache[key] = [];
}
cache[key].push(fn);
};
// 取消订阅(取消全部或者指定消息)
_unsubscribe = function (key, cache, fn) {
if (cache[key]) {
if (fn) {
for (var i = cache[key].length; i >= 0; i--) {
if (cache[key][i] === fn) {
cache[key].splice(i, 1);
}
}
} else {
cache[key] = [];
}
}
};
// 发布消息
_publish = function () {
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret = null,
stack = cache[key];
if (!stack || !stack.length) {
return;
}
return each(stack, function () {
return this.apply(_self, args);
});
};
// 创建命名空间
_create = function (namespace) {
var namespace = namespace || "default";
var cache = {},
offlineStack = {}, // 离线事件,用于先发布后订阅,只执行一次
ret = {
subscribe: function (key, fn, last) {
_subscribe(key, fn, cache);
if (!offlineStack[key]) {
offlineStack[key] = null;
return;
}
if (last === "last") { // 指定执行离线队列的最后一个函数,执行完成之后删除
offlineStack[key].length && offlineStack[key].pop()(); // [].pop => 删除一个数组中的最后的一个元素,并且返回这个元素
} else {
each(offlineStack[key], function () {
this();
});
}
offlineStack[key] = null;
},
one: function (key, fn, last) {
_unsubscribe(key, cache);
this.subscribe(key, fn, last);
},
unsubscribe: function (key, fn) {
_unsubscribe(key, cache, fn);
},
publish: function () {
var fn = null,
args = null,
key = _shift.call(arguments),
_self = this;
_unshift.call(arguments, cache, key);
args = arguments;
fn = function () {
return _publish.apply(_self, args);
};
if (offlineStack && offlineStack[key] === undefined) {
offlineStack[key] = [];
return offlineStack[key].push(fn);
}
return fn();
}
};
return namespace ? (namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret) : ret;
};
window.pubsub = {
create: _create, // 创建命名空间
one: function (key, fn, last) { // 订阅消息,只能单一对象订阅
var pubsub = this.create();
pubsub.one(key, fn, last);
},
subscribe: function (key, fn, last) { // 订阅消息,可多对象同时订阅
var pubsub = this.create();
pubsub.subscribe(key, fn, last);
},
unsubscribe: function (key, fn) { // 取消订阅,(取消全部或指定消息)
var pubsub = this.create();
pubsub.unsubscribe(key, fn);
},
publish: function () { // 发布消息
var pubsub = this.create();
pubsub.publish.apply(this, arguments);
}
};
})(window, undefined);
应用
假如我们正在开发一个商城网站,网站里有header头部、nav导航、消息列表、购物车等模块。这几个模块的渲染有一个共同的前提条件,就是必须先用ajax异步请求获取用户的登录信息。
至于ajax请求什么时候能成功返回用户信息,这点我们没有办法确定。更重要的一点是,我们不知道除了header头部、nav导航、消息列表、购物车之外,将来还有哪些模块需要使用这些用户信息。如果它们和用户信息模块产生了强耦合,比如下面这样的形式:
login.succ(function (data) {
header.setAvatar(data.avatar); // 设置header模块的头像
nav.setAvatar(data.avatar); // 设置导航模块的头像
message.refresh(); // 刷新消息列表
cart.refresh(); // 刷新购物车列表
});
现在登录模块是由你负责编写的,但我们还必须了解header模块里设置头像的方法叫setAvatar、购物车模块里刷新的方法叫refresh,这种耦合性会使程序变得僵硬,header模块不能随意再改变setAvatar的方法名。这是针对具体实现编程的典型例子,针对具体实现编程是不被赞同的。
等到有一天,项目中又新增了一个收货地址管理的模块,这个模块是由另一个同事所写的,此时他就必须找到你,让你登录之后刷新一下收货地址列表。于是你又翻开你3个月前写的登录模块,在最后部分加上这行代码:
login.succ(function (data) {
header.setAvatar(data.avatar);
nav.setAvatar(data.avatar);
message.refresh();
cart.refresh();
address.refresh(); // 增加这行代码
});
我们就会越来越疲于应付这些突如其来的业务要求,不停地重构这些代码。
用观察者模式重写之后,对用户信息感兴趣的业务模块将自行订阅登录成功的消息事件。当登录成功时,登录模块只需要发布登录成功的消息,而业务方接受到消息之后,就会开始进行各自的业务处理,登录模块并不关心业务方究竟要做什么,也不想去了解它们的内部细节。改善后的代码如下:
$.ajax('http:// xxx.com?login', function(data) { // 登录成功
pubsub.publish('loginSucc', data); // 发布登录成功的消息
});
// 各模块监听登录成功的消息:
var header = (function () { // header模块
pubsub.subscribe('loginSucc', function(data) {
header.setAvatar(data.avatar);
});
return {
setAvatar: function(data){
console.log('设置header模块的头像');
}
};
})();
var nav = (function () { // nav模块
pubsub.subscribe('loginSucc', function(data) {
nav.setAvatar(data.avatar);
});
return {
setAvatar: function(avatar) {
console.log('设置nav模块的头像');
}
};
})();
如上所述,我们随时可以把setAvatar的方法名改成setTouxiang。如果有一天在登录完成之后,又增加一个刷新收货地址列表的行为,那么只要在收货地址模块里加上监听消息的方法即可,而这可以让开发该模块的同事自己完成,你作为登录模块的开发者,永远不用再关心这些行为了。代码如下:
var address = (function () { // 地址模块
pubsub.subscribe('loginSucc', function(obj) {
address.refresh(obj);
});
return {
refresh: function(avatar) {
console.log('刷新收货地址列表');
}
};
})();
优缺点
优点
- 支持简单的广播通信,自动通知所有已经订阅过的对象;
- 页面载入后发布者很容易与订阅者存在一种动态关联,增加了灵活性;
- 发布者与订阅者之间的抽象耦合关系能够单独扩展以及重用。
缺点
- 创建订阅者本身要消耗一定的时间和内存,而且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中;
- 虽然可以弱化对象之间的联系,但如果过度使用的话,对象和对象之间的必要联系也将被深埋在背后,会导致程序难以跟踪维护和理解。
参考
- 《JavaScript设计模式与开发实践》 第 8 章 发布—订阅模式
- 《JavaScript设计模式》 第 9 章 第 5 节 Observer(观察者)模式
- http://www.cnblogs.com/TomXu/archive/2012/03/02/2355128.html
JavaScript设计模式与开发实践 - 观察者模式的更多相关文章
- 《JavaScript设计模式与开发实践》整理
最近在研读一本书<JavaScript设计模式与开发实践>,进阶用的. 一.高阶函数 高阶函数是指至少满足下列条件之一的函数. 1. 函数可以作为参数被传递. 2. 函数可以作为返回值输出 ...
- 《Javascript设计模式与开发实践》--读书笔记
第2章 this call apply bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用. bind( ...
- JavaScript设计模式与开发实践 - 策略模式
引言 本文摘自<JavaScript设计模式与开发实践> 在现实中,很多时候也有多种途径到达同一个目的地.比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路. 如果没有时间但 ...
- JavaScript设计模式与开发实践 - 单例模式
引言 本文摘自<JavaScript设计模式与开发实践> 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返 ...
- 《javascript设计模式与开发实践》--- (单一职责原则)
看的这本书叫<JavaScript设计模式与开发实践> 先规划一下看书的顺序,基础知识我已经大概的浏览了一遍了,没有留下笔记,以后有时间还会补上.本来打算顺着看的.但是我感觉我很难短时间内 ...
- JavaScript设计模式与开发实践——读书笔记1.高阶函数(上)
说来惭愧,4个多月未更新了.4月份以后就开始忙起来了,论文.毕设.毕业旅行等七七八八的事情占据了很多时间,毕业之后开始忙碌的工作,这期间一直想写博客,但是一直没能静下心写.这段时间在看<Java ...
- 《JavaScript设计模式与开发实践》读书笔记-基础知识
笔记内容多摘录自<JavaScript设计模式与开发实践>(曾探著),侵删. 面向对象的JavaScript 1. 动态需要类型和鸭子类型 鸭子类型 如果它走起路来像鸭子,叫起来也是鸭子, ...
- javascript设计模式与开发实践阅读笔记(8)——观察者模式
发布-订阅模式,也叫观察者模式:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. 在JavaScript开发中,我们一般用事件模型来替代传统的观察者模式. ...
- 《JavaScript设计模式与开发实践》读书笔记之观察者模式
1.观察者模式 观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知. JavaScript中通常采用事件模型替代传统的观察者模式 1.1 逐步实现观 ...
随机推荐
- html介绍
Html的介绍 Html 是一种标记语言,主要的用处是开发网页 展示出文字,图片,视频,声音等等 是我们web开发的基础 做什么 所谓的静态页面,指的是能不能交互,而不是东西能不能动 网页设计师(ht ...
- vim中如何替换
1) 文件内全部替换: :%s#abc#123#g (如文件内有#,可用/替换,:%s/abc/123/g) --注:把abc替换成123 (或者: %s/str1/str2/g 用str ...
- 课务IOS概述_1
1.网络 2.多线程 3.各种图形 4.动画 5.面向对象的数据库 Preconditions: 1.CS106 A和B 或CS106X 2.CS107或CS108更好 3.CS110就更好了 熟 ...
- SpringMVC拦截器2(资源和权限管理)(作为补充说明)
SpringMVC拦截器(资源和权限管理) 1.DispatcherServlet SpringMVC具有统一的入口DispatcherServlet,所有的请求都通过DispatcherServle ...
- HDU 2817 A sequence of numbers 整数快速幂
A sequence of numbers Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Ot ...
- IIS7多域名绑定同一物理目录,设置不同默认文档的解决方案
转载自 http://zzstudy.offcn.com/archives/6159 如何解决IIS7多域名绑定同一物理目录,设置不同的默认文档的问题? 因为在一个物理目录下只有一个web.confi ...
- GHOST系统锁定主页常用软件及解决方案
网络下载的GHOST系统,很多锁定主页,无法用360等更改,卸载里面也不含有锁定主页的软件名称.其中常用的锁定的主页的软件有: 1.百度组件. 一般在C盘含有BAIDU的文件夹内,找到卸载即可. 2. ...
- (1)创建一个叫做People的类: 属性:姓名、年龄、性别、身高 行为:说话、计算加法、改名 编写能为所有属性赋值的构造方法; (2)创建主类: 创建一个对象:名叫“张三”,性别“男”,年龄18岁,身高1.80; 让该对象调用成员方法: 说出“你好!” 计算23+45的值 将名字改为“李四”
package a; public class People { private String name,sex; private int age; private double height; pu ...
- 常用ADB命令
adb devices 查看当前已与pc端连接的设备序列号 adb install -r +apk名称 安装应用程序(带-r参数表示强制安装,可以覆盖安装) adb un ...
- CSS笔记(十)position属性与定位
参考:http://www.w3school.com.cn/cssref/pr_class_position.asp position属性规定了元素的定位类型.任何元素都可定位,其中,绝对或固定元素会 ...