关于观察者模式和发布/订阅模式

我看网上有些人说观察者模式和发布者/订阅者模式不好区分容易混淆,说是人们常常混为一谈,但是看《js设计模式》的时候,觉得二者的结构差异并不是很模糊的。

《设计模式》里面的观察者模式是:创建主体和观察者两个对象的功能(两个功能对象),然后把这两个对象的方法属性给予具体的实例如DOM或js对象,让后让具体实例来完成“主体状态改变--观察者执行响应”。

主要是主体有一个属性,是一个数组(比如叫observerList),用来存储关注她的观察者;以及一个notify()方法,每次状态改变都从observerList里面遍历一次,依次触发观察者的update方法。

他给出的观察者模式只有主体和观察者两个东西。

试写了一个简单的观察者模式的小例子:

      一个作为输入的input(即主体),一个add按钮给.container这个div添加input,并将添加的input作为观察者加入主体的观察者列表。

    当主体输入数字时,自动触发input事件,下边的input自动显示双倍数字。

 1 <!DOCTYPE html>
 2 <html lang="en">
 3 <head>
 4     <meta charset="UTF-8">
 5     <title>Observers</title>
 6 </head>
 7 <body>
 8     <input type="text" id="subect">
 9     <button id="addBut">add</button>
10     <div id="container">
11         
12     </div>
13 
14     <script>
15     //list
16     function Olist(arr){
17     this.olist=arr||[];
18     }
19     //list.add
20     Olist.prototype.add=function(obj){
21         this.olist.push(obj);
22     }
23     //list.length
24     Olist.prototype.count=function(){
25         return this.olist.length;
26     }
27     //list.get
28     Olist.prototype.get=function(i){
29         return this.olist[i];
30     }
31     //Subject
32     function Subject(){
33             this.observers=new Olist();
34         }
35     //Subject.add
36     Subject.prototype.addo=function(obj){
37         this.observers.add(obj)
38     }
39     //Subject.notify
40     Subject.prototype.notify=function(signal){
41         var l = this.observers.count();
42         for(var i=0;i<l;i++){
43             this.observers.get(i).update(signal);
44         }
45     }
46     //observer
47     function Observer(){
48 
49     }
50     //observer.update
51     Observer.prototype.update=function(signal){
52       this.value=Number(signal)*2;
53     }
54     
55     //extend
56     function extend(obj,extension){
57       for(var key in obj){
58           extension[key]=obj[key]
59       }
60     }
61     //getDOM
62     var s = document.getElementById("subect");
63     var a = document.getElementById("addBut");
64     var c = document.getElementById("container");
65     //Subject--s
66     extend(new Subject(),s);
67     //add observer
68     a.onclick=function(){
69         var son=document.createElement("input")
70         son.type="text";
71         son.value="";
72         extend(new Observer,son);
73         s.addo(son);
74        c.appendChild(son);
75 
76     }
77     //notify input-event
78     s.oninput=function(){
79         this.notify(this.value);
80     }
81  
82     </script>
83 </body>
84 </html>

 但是发布/订阅模式的实现是在两者中间多了一个pubsub对象,这个pubsub对象有publish()、subscribe()、unsubscribe()方法,客体先pubsub.subscribe()某个事件,当主体发布了某个事件时要调用pubsub.publish("topic"),然后在pubsub对象的闭包中遍历已存储的事件,如果已存储的事件中有"topic",就在订阅了topic的对象列表中依次触发它们的回调。

    从实现上看,pub/sub模式更像是对前者的改进,因为它实现了主体和观察者的解耦。

    这意味着,pubsub在代码结构中是一个独立的存在,内部存储着 被订阅的主题类型(英文是topic,但意义和事件类似) 及 该主题发布时的回调  ,只是对外暴露了三个核心方法

subscribe():作用是往pubsub中插入订阅的主题并创建该主题下的回调函数数组

publish():发布某个主题,搜寻是否有关于该主题的订阅,有的话就遍历执行该主题对应的回调函数列表

unsubscribe():取消对某个主题的订阅(将回调函数从该主题的回调函数列表中剔除)

在《js设计模式》示例9-2和9-4中,给的都是一个ui更新的例子。即数据更新后,发布一个“dataAvailable”主题并把更新的数据作为参数传入.publish()中。而subscribe(“dataAvailable”,callback)的回调中则往页面上添加html向用户显示数据。

最后一个示例针对ajax应用,一开始看觉得这代码的顺序有点乱,然后上网找到Addy Osmani在2011年写的文章:https://msdn.microsoft.com/en-us/magazine/hh201955.aspx

文章最后的示例是同一个,但排版结构比中译版更清晰......

实际上,我们可以认为.subscribe()的回调是对.publish()的响应,而在一个subscribe()回调中,我们也能publish()另一个主题,触发另外的操作任务。(实现了时间上的顺序执行和空间上的代码分离,但是这本质上就跟函数调用无二)

另外一点,对于同一个topic,可以有多个subscribe()回调, 如示例中对于“search/tags”这个主题,安插了两个任务,一个发起请求,一个在页面上显示提示信息。所以在等待请求返回期间ui上显示搜索中,请求返回后发布另一个“search/result”主题展示结果。

写了个简单的pubsub实现:

    <input type="button" value="click" id="cc">
 1 var p={};
 2 (function(p){
 3     var topics={},uid=0;
 4     var publish=function(topic,data){
 5       if(!topics[topic]){
 6           return false;
 7       } 
 8       var subers=topics[topic];
 9       var l=subers.length;
10       for(var i=0;i<l;i++){
11         subers[i].callback(topic,data);
12       }
13     }
14 
15     p.publish=function(topic,data){
16       publish(topic,data);
17     }
18     p.subscribe=function(topic,func){
19       if(!topics[topic]){
20           topics[topic]=[];
21       }
22       var token=(++uid).toString();
23       topics[topic].push({token:token,callback:func});
24       return token;
25     }
26 }(p))
27 
28 var cc=document.getElementById("cc");
29 //subscribe note1
30 var event_1=p.subscribe("note1",function(topic,data){
31   console.log(data + " has been clicked!")
32   //call note2
33    p.publish("note2");
34 })
35 //subscribe note2
36 var event_2=p.subscribe("note2",function(topic,data){
37   console.log("this is note2")
38   
39 })
40 cc.onclick=function(e){
41     var e = e||window.event;
42     var target = e.target || e.srcElement;
43     p.publish("note1",target.id);
44 }
View Code
原文地址:https://www.cnblogs.com/alan2kat/p/7683174.html