设计模式-发布-订阅模式
2016-04-21 本文已影响0人
寿_司
又叫做观察者模式,定义对象之中一种一对多的依赖关系,当一个对象的状态发生改变时候,所有依赖于它的对象都将得到通知。
一、应用
- 广泛应用于异步编程之中,是一种替代回调函数的方案。订阅一个事件,发生操作A(对操作A监听)之后,事件发生.
- 取代对象之间硬编码的通知机制,一个对象不用再显示的调用另外一个对象的某个接口,让两个对象松耦合的联系在一起,虽然不清楚彼此的细节,但是不影响他们之间相互通信。
1. Dom事件
一种典型的发布订阅者模式,一个事件(‘click,mousedown等’)对一个dom节点进行监听,操作dom节点,相应的触发事件,响应函数执行。事件函数对dom节点完全未知,不用去理会事件函数内容,发布就好。
2. 自定义事件
- 指定发布者
- 给发布者一个缓存列表,一个对象由n个键值对组成,键表示事件名,值是一个由事件处理程序组成的数组,相当于订阅者的花名册
- 发布消息,遍历缓存列表,依次执行订阅者的回调函数。
自定义事件SF
3. 全局的发布订阅模式
问题:
- 每一个发布者对象都有listen和trigger方法,以及一个缓存列表clientList,浪费资源
- 发布者和订阅者还有一定的耦合关系:
发布者.listen(“事件名”,function(){})
所以【对于全局模式下的订阅者】:
- 使用一个全局的Event对象来实现,订阅者和发布者不知道相互之间是谁,相当于一个传递消息的“中介”:
Event.listen("事件名",function(){})
4. 模块之间通信
- 全局模式下的订阅者而言,两个封装良好的模块之间的通信是通过Event对象来进行的,比如两个模块都对Event.count来进行处理,从而实现模块之间通信。
- 这样的情况之下,模块之间的联系被隐藏,所以需要首先定义好为其他模块之间通信服务的暴漏性模块,他的作用就是暴露出一部分接口,供其他模块使用。
5. 全局事件的命名冲突
//全局作用域下的发布订阅模式
(function(){
var Event = (function{
var global = this,
Modal,
_default = 'default';
Event = function(){
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function(ary,fn){
var ret ;
for(const i = 0,l = ary.length; i < l;i ++){
var n = ary[i];
ret = fn.call(n,i,n);
}
return ret;
};
_listen = function(key,fn,cache){
if(!cache[key]){
cache[key] = [];
}
cache[key].push(fn);
};
_remove = function(key,cache,fn){
if(cache[key]){
if(fn){
for(var i = cache[key].length;i>=0;i--){
if(cache[key] === fn){
cache[key].splice(i,1);
}
}
}else{
cache[key] = [];
}
}
};
_trigger = function(){
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
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 = {
listen:function(key,fn,last){
_listen(key,fn,cache);
if(offlineStack === null){
return;
}
if(last === 'last'){
offlineStack.length && offlineStack.pop()();
}else{
each(offlineStack,function(){
this();
});
}
offlineStack = null;
},
one:function(key,fn,last){
_remove(key,cache);
this.listen(key,cache,fn);
},
remove:function(key,fn){
_remove(key,cache,fn);
},
trigger:function(){
var fn,
args,
_self = this;
_unshift.call(arguments,cache);
args = arguments;
fn = function(){
return _trigger.apply(_self,args);
};
if(offlineStack){
return offlineStack.push(fn);
}
return fn;
}
};
return namespace ?
(namespaceCache[namespace] ? namespaceCache[namespace] : namespaceCache[namespace] = ret)
: ret;
};
return {
create : ,
one: ,
remove: ,
listen:,
trigger:,
var event = this.create();
event.trigger.apply(this,arguments);
}
}();
return Event;
}());
二、遇到的问题:
- 问题: 一个modal组件开发的过程中,业务逻辑编写完之后,开始进行组件化的整合,需要预留出一个可被实例化出一个组建的构造函数,一些开放字段,一些构造函数的原型方法。要求代码符合组件化规范,可复用,易扩展,松耦合
- 解决方案: 发布订阅模式,一个组件Modal当做发布者,里面所有的方法,预留参数当做订阅者,耦合性较松,封装又比较严密。
....未完待续