关于JavaScript的观察者模式已经有很多的牛人做过详细的描述了,如:司图正美,李平(Leepy)。Leepy的博客中对关于JavaScript的设计模式讲述的比较全面,如果大家有兴趣可以去拜读一下。
我对观察者模式只有一个粗浅的认识,还没有达到牛人的境界。对写这些技术性的文章不太在行总有些力不从心,如果有哪些地方写的不好欢迎各位拍砖;关于“观察者模式”我也是在网上和书里看了好久才对他有点儿了解,下面就把我个人的一点总结写出来。一来可以做为一个备用,二来也希望可以和各位牛人们多多学习一下。 ^_^
我喜欢把东西比喻的实物化一些,这样有助于记忆。我把“观察者模式”比喻成:将军和士兵的关系。这个怎么说呢?“将军”就好比是一位发布指令的“发布者”;士兵就好比是接收这些指令的“订阅者”。将军不可能只有一位,他们都可能会成为这个士兵的“发布者”。当将军做出一些决定(状态的改变)的时候就会将这些改变传递给士兵,让士兵按着将军的意图来完成。在现实生活中也会有这种事情发生,如上级给下级发派任务或是完成女友下达的任务,这些都是强制性的不能找任何的借口或是怨言,否则后果自负……T_T!
下面就来看看将军和士兵分别在观察者模式中都是如何定义的。
一、订阅者的定义,也就是士兵是如何接收指令的
“订阅者”就是要接收上级下达下来的指令,但他们不能胡乱接收,得有一套约定好的规矩,俗话说无规矩不成方圆嘛。那这样一来就需要有一个用来接收指令的接口(这个接口已经由上级批准且备案了的)。一些资料中都将这些接口定义为“update()”,我这里也姑且还延用这个方法名了。
// 订阅者接口 function Subscribers(){}; Subscribers.prototype = { update : function(){ alert('订阅者接口'); // 默认接口内容,在以后定义中进行重定义 } };
这个“订阅者接口”中定义了将要从上级下来的指令是通过“update()”方法来接收并做处理的。这只是一个原始状态还不能起到应有的功能,需要在后续的过程(以后所有的订阅者都会继承这个接口中的方法,也就是说只要是订阅者都会有这个update()方法来接收命令)中将其再一次重写来完成相应的功能。
二、订阅者完成了,现在就开始制定将军下发指令时的功能接口了
// 发布者接口 function Subject(){ this.observers = []; }; Subject.prototype = { notice : function(content){ for(var i = 0, len = this.observers.length; i < len; i++){ this.observers[i].update(content); } }, addObservers : function(observers){ this.observers.push(observers); }, removeObservers : function(observers){ var index = this.observers.indexOf(observers); this.observers.slice(index, 1); } };
将军下达命令要包括以下这几个基本的功能:
1、目前都有谁来受我指挥,也就是说订阅者都有谁?这就需要一个容器来存储这些订阅者,在“发布者接口”中用来存储这些订阅者的容器是一个数组(this.observers=[])。
2、订阅者有了那就需要发挥将军的职能了----发布指令。方法“notice()”就是用来向订阅他指令的士兵发送命令的,通过这个方法将军向士兵中已经约定好的接口(update()方法)发布指令,针对于程序中就是将军在这个notice()方法中调用每一个订阅他指令的士兵的update()方法来下达命令。
3、也可以有一个删除订阅者的方法。用以将不需要下达指令的士兵从订阅容器中清除。如程序中的removeObservers()方法就是来完成这个功能的。
三、订阅者接口与发布者接口都已经定义完成了,现在我们就来真正的制定一个用来接收指令的士兵
目前这个将军有2个士兵用以调遣,其中一个是用来显示下达指令后的提示信息;另一个是用来改变颜色。
var lis = document.getElementsByTagName('li'); // 我是一个兵,用来改变订阅者中的某个状态(弹出信息) var MsgInfo = function(subject){ this.subject = subject; this.subject.addObservers(this); }; //继承方法 extend(MsgInfo, Subscribers); MsgBox.prototype = { update : function(content){ for(var i = 0, len = lis.length; i < len; i++){ lis[i].onclick = (function(index){ return function(){ alert('我的索引号是第:'+(index+1)+' ,而且还有相同的显示信息:'+content); } })(i); } } }; // 我也是一个兵,用来改变订阅者中的某个状态(字体颜色) var MsgStyle = function(subject){ this.subject = subject; this.subject.addObservers(this); }; extend(MsgStyle, Subscribers); MsgStyle.prototype = { update : function(){ for(var i = 0, len = lis.length; i < len; i++){ lis[i].onmouseout = function(){ this.style.color = 'red'; }; } } };
四、现在就需要将士兵与将军绑定在一起来完成下达与接受的功能了
1、家有千口,主事一人。首先要让将军闪亮登场,他是这个台戏的主要的命令发布者,即实例化发布者。
2、各位士兵排除迎接,即实例化士兵。
3、将军与他的士兵们相互认识一下,建立合作关系,即绑定发布者与订阅者。
4、将军发布命令,士兵执行。
// 将军闪亮登场,实例化发布者 var ImplementSubject = new Subject(); // 士兵排除欢迎,实例化订阅者。同时与发布者相互认识并建立合作关系 var box = new MsgInfo(ImplementSubject); var boxStyle = new MsgStyle(ImplementSubject); // 将军发布指令,通过notice()来执行订阅者的update()方法 ImplementSubject.notice('我是订阅者!');
以上就完成了一个“发布者”与“订阅者”的模拟关系,我们可以通过这种模式来完成一些(一对多)功能。通过执行某一个元素的功能来完成其他元素状态的改变,从而达到了“发布者”和“订阅者”的关系。下面是一个完整的例子。