图片 2

观望者设计情势,学习笔记

JavaScript设计模式之观察者模式(学习笔记)

设计模式(Design
Pattern)对于软件开发来说其重要性不言而喻,代码可复用、可维护、可扩展一直都是软件工程中的追求!对于我一个学javascript的人来说,理解设计模式似乎有些困难,对仅切图、做少量交互效果的FE甚至可能不会用到,但是当你开始使用Angular/Backbone等框架的时候,就无法避免设计模式、MVC/MVVM这些东西了(反正我是伤脑筋)。

 

我学设计模式是刚开始接触编程大概三个月的时候,看一本书《大话设计模式》,里面用C#语言来写,我很无语,因为强类型的编程语言对于我这种写弱类型的毛头小子来说,似乎又有困难啊,于是我就学C#基础语法规则去了。。。今年年初我又学了JAVA的基础语法规则。。。然而我的初衷已经被抛弃在一旁,落上了厚厚的灰层。对于自学编程的我来说,不知道学习编程的先后顺序似乎吃亏不少,但是总要有开头的!

 

以上可直接跳过

 

先来说一下我对“观察者模式”的个人理解:观察者模式又称“发布-订阅(Publish/Subscribe)模式”,发布与订阅显然是两个不同对象的功能,比如RSS。知乎是一个发布者(发布一些对某方面问题的高赞同解答),我作为一个订阅者(在我的邮箱里面订阅了知乎的相关发布内容),我的同事以及我的老板都订阅了知乎,所以在这个模型中,有一个发布者,有三个订阅者。

 

在具体编程中,发布者有了新的内容,需要向订阅者推送数据,那么新的内容(state)、订阅者有哪些(observers)就是发布者需要包含的东西,谁订阅了、谁退订了则要对发布者中的订阅者列表进行更新。以下是发布者的相关信息代码解读:

 

 

//发布者
        function Publisher(){
            this.observers = [];
            this.state = "";

        }
        Publisher.prototype.addOb=function(observer){
            var flag = false;
            for (var i = this.observers.length - 1; i >= 0; i--) {
                if(this.observers[i]===observer){
                    flag=true;                
                }
            };
            if(!flag){
                this.observers.push(observer);
            }
            return this;
        }
        Publisher.prototype.removeOb=function(observer){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                if(observers[i]===observer){
                    observers.splice(i,1);
                }
            };
            return this;
        }
        Publisher.prototype.notice=function(){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                    observers[i].update(this.state);
            };
        }

 

 

以上在遍历observers数组的时候,可以使用数组类的filter、forEach等新特性来处理。第三个notice函数表示发布者有了新东西,然后对订阅者列表中的所有人通知他们我有新内容(state)了,你们拿去更新你们的邮箱吧。这里把内容传递给了每一个订阅者的update更新功能。

 

那么订阅者呢?订阅者很简单,只需要具有一个update功能即可(每一个订阅者update可能不一样,比如我是放进邮箱了,我的同事则将订阅的拿来,并且顺便把旧的删掉了,我的上司则将数据转发到Gmail去了)。下面是订阅者相关信息代码解读:

 

 

//订阅者
        function Subscribe(){
            this.update = function(data){
                  console.log(data);
            };
        }

 

 

实际上,因为每一个订阅者都有这个update,所以我们通常应该将其添加到构造器的原型上面,当对这个默认的update功能不满足要求的时候,可以为每一个订阅者的实例设置单独的update,比如将这个data发送给别人。最后咱们看看怎么应用。

 

 

//实际应用
        var oba = new Subscribe(),
            obb = new Subscribe();

        var pba = new Publisher();

        pba.addOb(oba);
        pba.addOb(obb);

        oba.update = function(state){
            console.log(state+"hello!");
        }
        obb.update = function(state){
            console.log(state+"world!");
        }
        pba.state = "open ";
        pba.notice();

 

 

大家看到,我们在最后对发布者手动设置了它的内容(state)并且要求他发出通知(notice)。在实际项目中,发布者的内容可能是从后台获取的也可能是从前台某地方输入的。然而发布者每次更新内容后又要手动调用通知是不是有点多余呢?既然更新了内容那就肯定要通知别人了啊。那我们就把内容的更新与发出通知进行绑定好了,看下面的代码:

 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <script type="text/javascript">
        //发布者
        function Publisher(){
            this.observers = [];
            var state = "";     //让该内容不能直接访问

            //新增两个对于state的操作 获取/更新
            this.getState=function(){
                return state;
            }
            this.setState=function(value){
                state = value;
                this.notice();
            }

        }
        Publisher.prototype.addOb=function(observer){
            var flag = false;
            for (var i = this.observers.length - 1; i >= 0; i--) {
                if(this.observers[i]===observer){
                    flag=true;                
                }
            };
            if(!flag){
                this.observers.push(observer);
            }
            return this;
        }
        Publisher.prototype.removeOb=function(observer){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                if(observers[i]===observer){
                    observers.splice(i,1);
                }
            };
            return this;
        }
        Publisher.prototype.notice=function(){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                    observers[i].update(this.getState()); //获取发布者的内容
            };
        }


        //订阅者
        function Subscribe(){
            this.update = function(data){
                  console.log(data);
            };
        }

        //实际应用
        var oba = new Subscribe(),
            obb = new Subscribe();

        var pba = new Publisher();

        pba.addOb(oba);
        pba.addOb(obb);

        oba.update = function(state){
            console.log(state+"hello!");
        }
        obb.update = function(state){
            console.log(state+"world!");
        }
        pba.setState("open "); //发布者更新了内容
    </script>
</body>
</html>

 

 

 

对于以上的内容,或许并没有跟我们的项目中实际出现的问题有关,那我们就来代入这种设计模式,做一个例子:三个文本框ABC,其中A可编辑,B与C不可编辑且B的值是A的值加上后缀”@w3c.com”,C的值是A的值加上前缀”ID-“。

 

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div>
        <label>用户名称:<input type="text" id="pba" placeholder="请输入用户名称" /></label><br /><br />
        <label>生成邮箱:<input type="text" id="oba" readonly /></label>
        <label>生成ID:<input type="text" id="obb" readonly /></label>
    </div>

    <script type="text/javascript">
        //发布者
        function Publisher(obj){
            this.observers = [];
            var state = obj.value;     //让该内容不能直接访问

            //新增两个对于state的操作 获取/更新
            this.getState=function(){
                return state;
            }
            this.setState=function(value){
                state = value;
                this.notice();
            }
            this.obj = obj;

        }
        Publisher.prototype.addOb=function(observer){
            var flag = false;
            for (var i = this.observers.length - 1; i >= 0; i--) {
                if(this.observers[i]===observer){
                    flag=true;                
                }
            };
            if(!flag){
                this.observers.push(observer);
            }
            return this;
        }
        Publisher.prototype.removeOb=function(observer){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                if(observers[i]===observer){
                    observers.splice(i,1);
                }
            };
            return this;
        }
        Publisher.prototype.notice=function(){
            var observers = this.observers;
            for (var i = 0; i < observers.length; i++) {
                    observers[i].update(this.getState());
            };
        }

        //订阅者
        function Subscribe(obj){
            this.obj = obj;
            this.update = function(data){
                this.obj.value = data;
            };
        }

        //实际应用
        var oba = new Subscribe(document.querySelector("#oba")),
            obb = new Subscribe(document.querySelector("#obb"));

        var pba = new Publisher(document.querySelector("#pba"));

        pba.addOb(oba);
        pba.addOb(obb);

        oba.update = function(state){
            this.obj.value = state+"@w3c.com";
        }
        obb.update = function(state){
            this.obj.value = "ID-"+state;
        }

        pba.obj.addEventListener('keyup',function(){
            pba.setState(this.value);
        });

    </script>
</body>
</html>

 

 

 

在《大话设计模式》一书中,提到类似的情况:如果针对发布者内容而订阅者要做不同的事情呢?比如一个按钮和三个矩形,点击按钮的时候,第一个矩形增加宽度,第二个矩形增加高度,第三个矩形则变成圆角矩形又该怎么做呢?当然我们可以在三个矩形的update内部写具体的实现代码,但是这update岂不是没有一个具体的功能描述了吗?该书中用“事件委托”解决了这个问题(此处事件委托和DOM中的事件委托应该是两码事),我个人理解这个“事件委托”在javascript中可以用一个数组表示,然后里面放各个订阅者的不同名字的update,然后一一调用。

 

在《javascript设计模式》一书中,关于观察者模式的实现也是采用”推“这种方式,章节的最后反问到如何实现”拉“这种方式呢?

 

我个人理解:发布者推送数据的时候有强制性,促使订阅者更新(update),然而在”拉“这种模式中,发布者本身仅仅包含最新的内容,没有通知(notice)没有订阅者列表,当订阅者需要得到数据的时候在其对应的update方法里面传入发布者对象即可。小白之见,请对该模式有不同理解的道友多多指正。o(∩_∩)o 

设计模式(Design
Pattern)对于软件开发来说其重要性不言而喻,代码可复用、可维护、可扩展一直…

观察者设计模式是一个好的设计模式,这个模式我们在开发中比较常见,尤其是它的变形模式订阅/发布者模式我们更是很熟悉,在我们所熟悉jQuery库和vue.js框架中我们都有体现。我在面试中也曾经被问到observer和它的变形模式publish/subscribe,说实话,当时有点懵。随着工作经历渐多,也认识到它的重要性,特别是当你想要朝着中高级工程师进阶时这个东西更是绕不过。

一、观察者模式

C#和Java这两大流行的编程语言,相似程度极高。本文就来欣赏一下C#委托和Java回调的异曲同工之妙。

设计模式中有一个非常著名的“观察者模式”,又称为“发布-订阅模式”。大概意思是一方发布消息,另一方订阅消息。订阅消息的一方不必等待消息的到来,而是径自干自己的事情去。如果发布消息的一方发布了新的消息,就会自动通知订阅者接收。

其实不必在意这种设计模式叫什么,事实上所谓的“观察者模式”的代码经常出现在我们编写的程序中,而直到计划写这篇文章时我才知道原来这就是大名鼎鼎的“观察者模式”。所以说编程的套路都是人们经验的结晶,之后不过再套上个炫酷的名字而已。

言归正传,下面我们分别用C#和Java设计出发布-订阅的效果。

定义

观察者设计模式中有一个对象(被称为subject)根据观察者(observer)维护一个对象列表,自动通知它们对状态的任何修改。

当一个subject要通知观察者一些有趣的事情时,它会向观察者发送通知(它可以包含通知主题相关的特定数据)

当我们不在希望某一特定的观察员被通知它们所登记的主题变化时,这个主题可以将他们从观察员名单上删除。

为了从整体上了解设计模式的用法和优势,回顾已发布的设计模式是非常有用的,这些设计模式的定义与语言无关。在GoF这本书中,观察者设计模式是这样定义的:

“一个或多个观察者对某一subject的状态感兴趣,并通过附加它们自己来注册它们对该主题的兴趣。当观察者可能感兴趣的主题发生变化时,会发送一个通知信息,该通知将调用们个观察者中的更新方法。当观察者不再对主题的状态感兴趣时,他们可以简单地分离自己。”

二、订阅者部分

通常订阅者都是用户,对于用户来说,打算订阅一种消息,最简单又合理的做法应该是:

  1. 告诉发布者我要订阅哪一条消息;
  2. 把我的消息处理方法提交给发布者。

这就好比订阅一份报纸时,先告诉报社我要订哪一种报纸,再告诉报社我家邮筒的地址。

让我们先用C#试试如何实现:

namespace PublisherSubscriberDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            Publisher publisher = new Publisher();
            publisher.SomeMessage += message => { Console.WriteLine("Subscriber 1: " + message); };
            publisher.SomeMessage += message => { Console.WriteLine("Subscriber 2: " + message); };
            publisher.Start();
        }
    }
}

为了使代码简洁,我们利用C#的lambda表达式把上面提到的两步工作写到了一行中(否则就要单独定义消息处理函数)。这行代码的效果是,把等号右侧的lambda表达式所代表的消息处理方法添加到发布者的SomeMessage消息的订阅列表中。发布者的订阅列表SomeMessage是一个委托(delegate),它可以添加所有订阅者的消息处理方法。这里我们让订阅者订阅了两次该消息,以代表多个订阅者的情况。

如果用Java实现大概是这个样子:

public class PublisherSubscriberDemo {
    public static void main(String[] args) {
        Publisher publisher = new Publisher();
        publisher.addOnMessageArriveListener(message -> { System.out.println("Subscriber 1:" + message); });
        publisher.addOnMessageArriveListener(message -> { System.out.println("Subscriber 2:" + message); });
        publisher.start();
    }
}

代码量基本与C#相同。这里使用了Java
8的lambda表达式使代码更简洁(否则就要用到匿名内部类)。由于Java中没有委托,所以只能用发布者提供的addOnMessageArriveListener把消息处理方法传递进去。

组成

扩展我们所学,以组件形式实现observer模式:

主题(subject):维护一个观察者列表,方便添加或删除观察者

观察者(observer):为需要通知对象更改状态的对象提供一个更新接口

实际主题(ConcreteSubject):向观察者发送关于状态变化的通知,存储实际观察者的状态

实际观察者(ConcreteObserver):存储引用到的实际主题,为观察者实现一个更新接口,以确保状态与主题的一致。

三、发布者部分

对于发布者来说,需要做的工作也有两条:

  1. 接收订阅者的消息处理方法;
  2. 在适当的时候调用这些方法。

其效果就相当于订阅者的消息处理方法在消息抵达的时候自动执行了,而实质上则是发布者手动调用了这些方法。

先来看C#的代码实现:

namespace PublisherSubscriberDemo
{
    class Publisher
    {
        public delegate void Message(string message);
        public Message SomeMessage;

        public void Start()
        {
            while (true)
            {
                SomeMessage("Some message from publisher.");
                System.Threading.Thread.Sleep(1000);
            }

        }
    }
}

是不是感觉很清爽?C#在编码效率上做的非常贴心,这里用的委托,可以接收任意数量的消息处理方法,只要这些方法和Message的参数及返回值相同。并且在调用SomeMessage时只需要写一次,就可以调用所有订阅者的消息处理方法。

相比之下,Java的代码就显得臃肿一些:

public class Publisher {

    private List<MessageArriveListener> listeners = new ArrayList<MessageArriveListener>();

    public interface MessageArriveListener {
        void MessageArrived(String message);
    }

    public void addOnMessageArriveListener(MessageArriveListener listener) {
        listeners.add(listener);
    }

    public void start()
    {
        while (true) {
            for (MessageArriveListener listener : listeners) {
                listener.MessageArrived("Some message from publisher.");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}

我们需要手动设计一个List来保存所有的消息处理方法(在Java中通常称为监听器),然后在适当的时候遍历这个列表一一通知订阅者。当然,Java的优点是代码语法简单,容易理解。比如所谓的监听器就是一个自定义接口,订阅者传进来的消息处理方法必须是一个实现该接口的类(通常是匿名内部类或lambda表达式)。

实现

1.对一个subject可能拥有的观察者列表进行建模:

function ObserverList(){
  this.observerList = [];
}

ObserverList.prototype.add = function( obj ){
  return this.observerList.push( obj );
};

ObserverList.prototype.count = function(){
  return this.observerList.length;
};

ObserverList.prototype.get = function( index ){
  if( index > -1 && index < this.observerList.length ){
    return this.observerList[ index ];
  }
};

ObserverList.prototype.indexOf = function( obj, startIndex ){
  var i = startIndex;

  while( i < this.observerList.length ){
    if( this.observerList[i] === obj ){
      return i;
    }
    i++;
  }

  return -1;
};

ObserverList.prototype.removeAt = function( index ){
  this.observerList.splice( index, 1 );
};

2.对subject进行建模,并在观察者列表中补充添加、删除、通知观察者的方法

function Subject(){
  this.observers = new ObserverList();
}

Subject.prototype.addObserver = function( observer ){
  this.observers.add( observer );
};

Subject.prototype.removeObserver = function( observer ){
  this.observers.removeAt( this.observers.indexOf( observer, 0 ) );
};

Subject.prototype.notify = function( context ){
  var observerCount = this.observers.count();
  for(var i=0; i < observerCount; i++){
    this.observers.get(i).update( context );
  }
};

3.为创建一个新的观察者定义一个框架。框架中的update功能将被稍后的自定义行为覆盖

// The Observer
function Observer(){
  this.update = function(){
    // ...
  };
}

四、运行结果

C#代码和Java代码的运行效果完全一致,都是每秒钟打印两句话。

图片 1

C#结果

图片 2

Java结果

演示代码已上传GitHub仓库:
C#:https://github.com/jingedawang/PublisherSubscriberDemo-CSharp
Java:https://github.com/jingedawang/PublisherSubscriberDemo-Java

示例

使用上面定义的观察者组件,我们做一个demo,定义如下:

  • 在页面中添加新的可观察复选框的按钮;
  • 一个控制复选框将作为一个subject,通知其它的复选框,它们应该被检查;
  • 正在被添加的复选框容器
  • 然后,我们定义实际的主题和实际的观察者处理句柄,以便为页面添加新的观察者并实现更新接口。

实例代码如下:

html

<button id="addNewObserver">Add New Observer checkbox</button>
<input id="mainCheckbox" type="checkbox"/>
<div id="observersContainer"></div>

js

// 用extend()扩展一个对象
function extend( obj, extension ){
  for ( var key in extension ){
    obj[key] = extension[key];
  }
}

// DOM 元素的引用
var controlCheckbox = document.getElementById( "mainCheckbox" ),
  addBtn = document.getElementById( "addNewObserver" ),
  container = document.getElementById( "observersContainer" );

// 实际主题 (Concrete Subject)
// 将控制 checkbox 扩展到 Subject class
extend( controlCheckbox, new Subject() );

// 单击checkbox 通知将发送到它的观察者
controlCheckbox.onclick = function(){
  controlCheckbox.notify( controlCheckbox.checked );
};

addBtn.onclick = addNewObserver;

// 实际观察者(Concrete Observer)
function addNewObserver(){

  // 新创建的checkbox被添加
  var check = document.createElement( "input" );
  check.type = "checkbox";

  // 扩展 checkbox 用 Observer class
  extend( check, new Observer() );

  // 用自定义的 update 行为覆盖默认的
  check.update = function( value ){
    this.checked = value;
  };

  // 添加新的 observer 到 observers 列表中
  // 为我们的 main subject
  controlCheckbox.addObserver( check );

  // Append the item to the container
  container.appendChild( check );
}

在这个示例中我们研究了如何实现和使用观察者模式,涵盖了主题(subject),
观察者(observer),实际/具体对象(ConcreteSubject),实际/具体观察者(ConcreteObserver)

效果演示:demo

五、参考资料

c#
event关键字的意义
lulu_jiang
Java
Lambda表达式入门
Constantin Marian Alin

观察者和发布者订阅模式之间的差异

虽然,观察者模式很有用,但是在JavaScript中我们经常会用一种被称为发布/订阅模式这种变体的观察者模式。虽然它们很相似,但是这些模式之间还是有区别的。

观察者模式要求希望接受主题通知的观察者(或对象)必须订阅该对象触发事件的对象(主题)

然而,发布/订阅模式使用一个主题/事件通道,该通道位于希望接受通知(订阅者)和触发事件(发布者)的对象之间。此事件系统允许代码定义特定用于应用程序的事件,这些事件可以通过自定义参数来传递订阅者所需的值。这样的思路是为了避免订阅者和发布者的依赖关系。

与观察者模式不同,它允许任何订阅者实现一个适当的事件处理程序来注册并接收发布者发布的主题通知。

下面一个例子提供了功能实现,使用发布/订阅模式,可以支持在幕后的publish(),subscribe(),unsubscribe()

// 一个简单的邮件处理程序
// 接收邮件数
var mailCounter = 0;

// 初始化监听主题的名为 "inbox/newMessage" 的订阅者.

// 呈现一个新消息的预览
var subscriber1 = subscribe( "inbox/newMessage", function( topic, data ) {

  // 为了调试目的打印 topic
  console.log( "A new message was received: ", topic );

  // 使用从我们的主题传递的数据并向订阅者显示消息预览
  $( ".messageSender" ).html( data.sender );
  $( ".messagePreview" ).html( data.body );

});

// 这是另一个订阅者使用相同数据执行不同的任务.

// 更新计数器,显示通过发布者发布所就收的消息数量

var subscriber2 = subscribe( "inbox/newMessage", function( topic, data ) {

  $('.newMessageCounter').html( ++mailCounter );

});

publish( "inbox/newMessage", [{
  sender: "hello@google.com",
  body: "Hey there! How are you doing today?"
}]);

// 我们可以在取消订阅让我们的订阅者不能接收到任何新的主题通知如下:
// unsubscribe( subscriber1 );
// unsubscribe( subscriber2 );

它的用来促进松散耦合。它们不是直接调用其他对象的方法,而是订阅另一个对象的特定任务或活动,并在发生改变时得到通知。

优势

观察者和发布/订阅模式鼓励我们认真考虑应用程序的不同部分之间的关系。他们还帮助我们确定那些层次包含了直接关系,而那些层次则可以替换为一系列的主题和观察者。这可以有效地将应用程序分解为更小的、松散耦合的块,以改进代码管理和重用潜力。使用观察者模式的进一步动机是,我们需要在不适用类紧密耦合的情况下保持相关对象间的一致性。例如,当对象需要能够通知其他对象是,不需要对这些对象进行假设。

在使用任何模式时,观察者和主题之间都可以存在动态关系。这题懂了很大的灵活性,当我们的应用程序的不同部分紧密耦合时,实现的灵活性可能不那么容易实现。

虽然它不一定是解决所有问题的最佳方案,但这些模式仍然是设计解耦系统的最佳工具之一,并且应该被认为是任何javascript开发人员的工具链中最重要的工具。

劣势

这些模式的一些问题主要源于他们的好处。在发布/订阅模式中,通过将发布者与订阅者分离,有时很保证我们的应用程序的某些特定部分可以像我们预期的那样运行。

例如,发布者可能会假设一个或多个订阅者正在监听他们。假设我们使用这样的假设来记录或输出一些应用程序的错误。如果执行日志记录崩溃的订阅者(或者由于某种原因不能正常运行),那么由于系统的解耦特性,发布者将无法看到这一点。

这种情况的另一种说法是,用户不知道彼此的存在,对交换发布者的成本视而不见。由于订阅者和发布者之间的动态关系,更新依赖关系可能很难跟踪。

发布/订阅模式的实现

发布/订阅在JavaScript生态系统中很适用,这在很大程度上是因为在核心的ECMAScript实现是事件驱动的。在浏览器环境中尤其如此,因为DOM将事件作为脚本的主要交互API。

也就是说,ECMAScript和DOM都不提供在实现代码中创建自定义事件系统的核心对象或方法(可能只有DOM3
CustomEvent,它是绑定到DOM的,不是通用)。

幸运的是,流行的JavaScript库,如dojo、jQuery(自定义事件)和YUI已经有了一些实用工具,它们可以帮助轻松实现发布/订阅系统。下面我们可以看到一些例子:

var pubsub = {};

(function(myObject) {

    // Storage for topics that can be broadcast
    // or listened to
    var topics = {};

    // A topic identifier
    var subUid = -1;

    // Publish or broadcast events of interest
    // with a specific topic name and arguments
    // such as the data to pass along
    myObject.publish = function( topic, args ) {

        if ( !topics[topic] ) {
            return false;
        }

        var subscribers = topics[topic],
            len = subscribers ? subscribers.length : 0;

        while (len--) {
            subscribers[len].func( topic, args );
        }

        return this;
    };

    // Subscribe to events of interest
    // with a specific topic name and a
    // callback function, to be executed
    // when the topic/event is observed
    myObject.subscribe = function( topic, func ) {

        if (!topics[topic]) {
            topics[topic] = [];
        }

        var token = ( ++subUid ).toString();
        topics[topic].push({
            token: token,
            func: func
        });
        return token;
    };

    // Unsubscribe from a specific
    // topic, based on a tokenized reference
    // to the subscription
    myObject.unsubscribe = function( token ) {
        for ( var m in topics ) {
            if ( topics[m] ) {
                for ( var i = 0, j = topics[m].length; i < j; i++ ) {
                    if ( topics[m][i].token === token ) {
                        topics[m].splice( i, 1 );
                        return token;
                    }
                }
            }
        }
        return this;
    };
}( pubsub ));

简单实现如下:

// Return the current local time to be used in our UI later
getCurrentTime = function (){

   var date = new Date(),
         m = date.getMonth() + 1,
         d = date.getDate(),
         y = date.getFullYear(),
         t = date.toLocaleTimeString().toLowerCase();

        return (m + "/" + d + "/" + y + " " + t);
};

// Add a new row of data to our fictional grid component
function addGridRow( data ) {

   // ui.grid.addRow( data );
   console.log( "updated grid component with:" + data );

}

// Update our fictional grid to show the time it was last
// updated
function updateCounter( data ) {

   // ui.grid.updateLastChanged( getCurrentTime() );
   console.log( "data last updated at: " + getCurrentTime() + " with " + data);

}

// Update the grid using the data passed to our subscribers
gridUpdate = function( topic, data ){

  if ( data !== undefined ) {
     addGridRow( data );
     updateCounter( data );
   }

};

// Create a subscription to the newDataAvailable topic
var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate );

// The following represents updates to our data layer. This could be
// powered by ajax requests which broadcast that new data is available
// to the rest of the application.

// Publish changes to the gridUpdated topic representing new entries
pubsub.publish( "newDataAvailable", {
  summary: "Apple made $5 billion",
  identifier: "APPL",
  stockPrice: 570.91
});

pubsub.publish( "newDataAvailable", {
  summary: "Microsoft made $20 million",
  identifier: "MSFT",
  stockPrice: 30.85
});

用户接口通知

接下来我们假设有一个web应用程序负责显示实时股票信息。

应用程序可能有一个网格用于显示股票统计数据和显示最新更新点的计数器。当数据模型发生变化时,应用程序将需要更新网格和计数器。在这个场景中,我们的主题(将发布主题/通知)是数据模型,我们的订阅者是网格和计数器。

当我们的订阅者收到通知时,模型本身已经更改,他们可以相应地更新自己。

在我们的实现中,我们的订阅用户将收主题“newDataAvailable”,以了解是否有新的股票信息可用。如果一个新的通知发布到这个主题,它将触发gridUpdate向包含该信息的网格添加一个新的行。它还将更新上一次更新的计数器,以记录上一次添加的数据

// Return the current local time to be used in our UI later
getCurrentTime = function (){

   var date = new Date(),
         m = date.getMonth() + 1,
         d = date.getDate(),
         y = date.getFullYear(),
         t = date.toLocaleTimeString().toLowerCase();

        return (m + "/" + d + "/" + y + " " + t);
};

// Add a new row of data to our fictional grid component
function addGridRow( data ) {

   // ui.grid.addRow( data );
   console.log( "updated grid component with:" + data );

}

// Update our fictional grid to show the time it was last
// updated
function updateCounter( data ) {

   // ui.grid.updateLastChanged( getCurrentTime() );
   console.log( "data last updated at: " + getCurrentTime() + " with " + data);

}

// Update the grid using the data passed to our subscribers
gridUpdate = function( topic, data ){

  if ( data !== undefined ) {
     addGridRow( data );
     updateCounter( data );
   }

};

// Create a subscription to the newDataAvailable topic
var subscriber = pubsub.subscribe( "newDataAvailable", gridUpdate );

// The following represents updates to our data layer. This could be
// powered by ajax requests which broadcast that new data is available
// to the rest of the application.

// Publish changes to the gridUpdated topic representing new entries
pubsub.publish( "newDataAvailable", {
  summary: "Apple made $5 billion",
  identifier: "APPL",
  stockPrice: 570.91
});

pubsub.publish( "newDataAvailable", {
  summary: "Microsoft made $20 million",
  identifier: "MSFT",
  stockPrice: 30.85
});

其它设计模式相关文章请转‘大处着眼,小处着手’——设计模式系列