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


    我看网上有些人说观察者模式和发布者/订阅者模式不好区分容易混淆,说是人们常常混为一谈,但是看《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
  • 相关阅读:
    Pycharm快捷键
    unittest自动化测试框架
    Python简介
    Git工作流介绍
    GitFlow ⼯作流
    go 整分钟开始执行程序
    vue 保留两位小数
    vue 格式化时间戳
    Supervisor-进程守护工具
    为什么计算机语言中的变量名都不能以数字开头呢?
  • 原文地址:https://www.cnblogs.com/alan2kat/p/7683174.html
Copyright © 2020-2023  润新知