5分钟读书笔记之 - 设计模式 - 观察者模式

在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式是一种管理人与其任务(确切的讲,是对象及其行为和状态之间的关系)之间的关系的得力工具。用javascript的话来讲,这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生变化时可以得到通知。

观察者模式中存在俩个角色:观察者和被观察者。也可以叫做发布者和订阅者。这种模式在javascript中有几种不同的实现方法,下面将对其中的一些实现方式进行考察。

模式的实践:

在javascript中有多种方法可以实现发布者-订阅者模式,在展示这些示例之前,我们先确保各种角色的扮演者(对象)及其行为(方法)都已经就绪。

  • 订阅者可以订阅和退订。他们还要接收。他们可以在“由人投送”和“自己收取”之间进行选择。
  • 发布者负责投送。他们可以在“送出”和“由人取”之间进行选择。

下面是一个发布者和订阅者之间的互动过程的高层示例。

var Publisher = new Observable;
var Subscriber = function (news) {
    //news delivered directly to my proch;
}
Publisher.subscribeCustomer(Subscriber);
Publisher.deliver(‘extre,extre,read all about it!‘);
Publisher.unSubscribeCustomer(Subscriber);

在这个模型中可以看出,发布者处于明显的主导。他们负责登记其客户,而且有权停止投送。最后新的报纸出版后它们会将其投送给客户。

上面的代码创建了一个新的可观察对象(Observable)。它有三个实例方法:subscribeCustomer、unSubscribeCustomer和 deliver。subscribeCustomer方法以一个订阅者的回调函数为参数。deliver方法在调用过程中将通过这些回调函数把数据发送给每一个订阅者。

下面的例子处理同一类问题,但是发布者和订阅者之间的互动方式有所不同。

var newYorkTime = new Publisher;
var AustinHerald = new Publisher;
var SfChronicle = new Publisher;

var Jon = function (from) {
    console.log(‘Delivery from‘+from+‘to jon‘);
}
var Lindsay = function (from) {
    console.log(‘Delivery from‘+from+‘to Lindsay‘);
}
var Quadaras = function (from) {
    console.log(‘Delivery from‘+from+‘to Quadaras‘);
}

jon.
    subscribe(newYorkTime).
    subscribe(SfChronicle);

Lindsay.
    subscribe(newYorkTime).
    subscribe(AustinHerald).
    subscribe(SfChronicle);

Lindsay.
    subscribe(newYorkTime).
    subscribe(SfChronicle);

newYorkTime.deliver(‘Here is your paper!‘);
AustinHerald.deliver(‘News‘).deliver(‘REviews‘).deliver(‘Coupons‘);
SfChronicle.deliver(‘the weather is sill chilly‘).deliver(‘hello..‘);

在这个例子中,发布者的创建方式和订阅者接收数据的方式没有多少改变,但是用于订阅和退订权的一方变成了订阅者,当然,负责发送数据的还是发布者一方。

本类中的发布者是 Publisher 的实例。他有一个deliver方法。而作为订阅者的函数对象则拥有subscribe和unsubscribe俩个方法。订阅者只是普通的回调函数,这俩个方法是通过扩展Function的prototype而加入的,下面我们将一步一步的构建符合需要的API。

构建观察者API:

function Publisher(){
    this.subscribe = [];
}

所有Publisher实例都应该能够投送数据。只要把deliver方法添加到Publisher的prototype中,他就能被所有Publisher对象共享:

Publisher.prototype.deliver = function (data) {
    this.subscribes.forEach(
        function (fn) {
            fn(data);
        }
    );
    return this;
}

这个方法使用的是javascript 1.6中新加的数组方法forEach逐一处理每一个订阅者。forEach方法会对一个“草垛”(haystack)从头到尾访问一遍,把每一根针,针的索引和整个数组提供给一个回调方法。订阅者数组中的每根针都是一个回调函数,比如Joe,Lindsay,Quadaras。

deliver 方法把this用作返回值,因此可以对该方法进行链式调用。

下一步是给以订阅者订阅的能力。

Function.prototype.subscribe = function (publisher) {
    var _this = this;
    var alreadyExists = publisher.subscribes.some(
        function (el) {
            return el===_this;
        }
    );
    if (!alreadyExists) {
        publisher.subscribes.push(this);
    }
    return this;
}

这段代码为Function的prototype添加了一个以Publisher对象为参数的subscribe方法。所有函数都能调用这个方法。subscribe方法先定义了一个this变量,并把this赋给它。后面用作数组的some方法参数的那个匿名函数将通过闭包机制访问到这个变量,从而访问到用以调用subscribe方法的那个函数对象。

some 也是javascript 1.6中新增加的数组方法,它以一个回调函数为参数,some逐一访问数组的各个元素,并以其为参数调用那个回调函数,只要至少有一次调用函数时返回true,则some方法返回ture,否则some方法返回false。subscribe把some方法的返回值赋值给alreadyExists,然后根据这个变量的值决定是否为指定的发布者添加一个订阅者。最后subscribe方法返回this,支持链式调用。

unsubscribe方法可供订阅者用来停止对事件发布者的观察:

Function.prototype.unsubscribe = function (publisher) {
    var _this = this;
    publisher.subscribes = publisher.subscribes.filter(
        function (el) {
            return el !== _this;
        }
    );
    return this;
}

有订阅者在监听到某种一次性的事件之后会在回调阶段立即退订该事件。其做法大致如下:

var publisherObject = new Publisher();
var observerObject = function (data) {
    //process data
    console.log(data);
    //unsubscribe from this publisher
    arguments.callee.unsubscribe(publisherObject);
}
observerObject.subscribe(publisherObject);

在现实世界中,观察者模式对于那种由许多javascript程序员合作开发的大型程序特别有用。它可以提高API的灵活性,使并行开发的多个实现能够彼此独立地进行修改。作为开发人员,你可以对自己的应用程序中什么是“令人感兴趣的时刻”做出决定。你所监听的不再是click、load、blur和mouseover等浏览器事件,在富用户界面应用程序中,drag,dropmoved,complete和tabSwitch都可能是令人感兴趣的事件。他们都是在普通浏览器事件的基础上抽象出来的可观察事件,可由发布者对象向其监听者广播。

实例动画:

动画是在应用程序中实现可观察对象的一个很好的起点。眨眼之间你就可以想出至少3个可观察的时刻:开始、结束和进行中。在本例中,我们将分别称之为onStart、onComplete、onTween。下面的代码演示了用前面编写的Publisher工具实现这些事件的过程。

var Animation = function (o) {
    this.onStart = new Publisher,
        this.onComplete = new Publisher,
        this.onTween = new Publisher;
}

Animation.method(‘fly‘, function () {
    //begin animation
    this.onStart.deliver();
    for (...) { //loop through frames
        //deliver frame number
        this.onTween.deliver(i);
    }
    //end animation
    this.onComplete.deliver();
});

//setup an account with the animation manager
var Superman = new Animation({config});

//Begin implementing subscribers
var putOnCape = function (i) {}
var takeOffCape =  function (i) {}

putOnCape.subscribe(Superman.onStart);
takeOffCape.subscribe(Superman.onComplete);

//fly can be called anywhere
Superman.fly();
//for instance:
addEvent(element, ‘click‘, function () {
    Superman.fly();
});

可以看到,如果你是负责实现为超人披上斗篷和解下斗篷的功能的人的话,这种运作方式还真不错。借助于发布者,你可以知道超人什么时候起飞以及什么时候回到地面,你只需预定这些时刻的通知便万事大吉了。

在DOM脚本编程环境中的高级事件模式中,事件监听器说到底是一种内置的观察者。事件处理器handler与事件监听器listener并不是一回事。前者说穿了就是一种把事件传给与其相关联的函数的手段,而且在这种模型中一种事件只能制定一个回调方法。而在监听器模式中,一个事件可以与几个监听器关联。每个监听器都能独立于其他监听器而改变。打个比方,对SanFrancisco Chronicle这家报社来说 ,其订阅者joe定没有定New York Times都无所谓。同样,joe不在在乎Lindsay是否也订阅了该报纸。每一方都只管处理自己的数据和相关行为。

例如,使用事件监听器,可以让多个函数响应同一个事件:

var el = $(‘example‘);
var fn1 = function(e){}
var fn2 = function(e){}
addEvent(el,‘click‘,fn1)
addEvent(el,‘click‘,fn2)

但是事件处理器就办不到

var el = $(‘example‘);
var fn1 = function(e){}
var fn2 = function(e){}
el.onclick = fn1;
el.onclick = fn2;

第一个例子中,使用事件监听器,所以fn1和fn2都会被调用。而第二个例子则第二次对onclick赋值会覆盖掉第一次,只会调用fn2。

言归正传,监听器和观察者之间的共同之处显而易见,实际上他们互为同义语。他们都订阅特定的事件,然后等待事件的发生,事件发生的时候,订阅方的回调函数会得到通知。传给他们的参数是一个事件对象,其中包含着事件发生时间,事件类型和事件发源地等有用的信息。

观察者模式适用场合:

如果希望把人的行为和应用程序的行为分开,那么观察者模式正适合这种场合。最好不要实现一些与用户操作绑定在一起而且来源于浏览器的东西,比如 click,keypress之类的基本DOM事件。对于那些只关心动画的开始,或者发现错别字的程序员而言,那些事件提供不了什么有用信息。

举例来说,用户点击导航的一个标签tab时,会打开一个包含更多信息的菜单。当然你可以直接监听这个click,不过这需要知道监听的是哪个元素,这样做的另一个弊端是你的实现与click事件直接绑在了一起。比监听click更好的做法是:创建一个可观察的onTabChange对象。并且在特定事件发生时通知所有观察者。如果菜单改为在鼠标指向标签时或者标签处于焦点之下时打开,那么这个onTabChange对象会替你处理这种改变。

观察者模式是开发基于行为的大型应用程序的有力手段。在一次浏览器会话期间,应用程序中可能会断断续续发生几十次事件,你可以削减为事件注册监听器的次数,让可观察对象借助一个事件监听器替你处理各种行为并将信息委托delegate给她的所有订阅者。从而降低内存消耗和提高性能。这样一来,就不用没完没了地为同样的元素添加新的事件监听器。

创建可观察对象会带来加载时间的开销。但是这可以采用惰性加载技术化解。具体来说就是把新的可观察对象的实例化推迟到需要发送事件通知的时候,这样一来,订阅者在事件尚未创建的时候就能订阅它,应用程序的初始加载时间也就不会受到影响。

时间: 2024-10-12 01:08:34

5分钟读书笔记之 - 设计模式 - 观察者模式的相关文章

5分钟读书笔记之 - 设计模式 - 门面模式

门面模式有俩个作用: 简化类的接口 消除类与使用它的客户代码之间的耦合 在javascript中,门面模式常常是开发人员最亲密的朋友.它是几乎所有javascript库的核心原则,门面模式可以使库提供的工具更容易理解.使用这种模式,程序员可以间接地与一个子系统打交道,与直接访问子系统相比,这样做更不容易出错. addEvent函数是一个基本的门面,你不用在每次为一个元素添加事件监听器的时候都得针对浏览器间的差异进行检查,有了这个便利,你可以把这个添加事件的底层细节抛在脑后,而把心思集中在如何构建

5分钟读书笔记之 - 设计模式 - 装饰者模式

本章讨论的是一种为对象增添特性的技术,它并不使用创建新子类这种手段. 装饰者模式可以透明地把对象包装在具有同样接口的另一对象之中,这样一来,你可以给一些方法添加一些行为,然后将方法调用传递给原始对象.相对于创建子类来说,使用装饰者模式对象是一种更灵活的选择. 装饰者可用于为对象增加功能.它可以用来替代大量子类. 考虑前面的自行车类,你现在可能提供一些配件供用户选择,装饰者模式要求我们只需要创建选件类,这些类与四种自行车类都要实现Bicycle接口,但是他们只被用作这些自行车类的包装类.在这个例子

5分钟读书笔记之 - 设计模式 - 适配器模式

适配器模式可以用来在现在接口和不兼容的类之间进行适配. 使用这种模式的对象又叫包装器,因为他们是在用一个新接口包装另一个对象. 在设计类的时候往往遇到有些接口不能与现有api一同使用的情况,借助于适配器,你可以不用直接修改这些类也能使用他们. 适配器的特点: 适配器可以被添加到现有代码中以协调俩个不同的接口.从表面上来看,适配器模式很像门面模式,他们都要对别的对象进行包装并改变其呈现的接口,二者之间的差别在于他们如何改变接口.门面元素展现的是一个简化接口,它并不提供额外的选择,而且有时是为了方便

5分钟读书笔记之 - 设计模式 - 单体模式

单体是一个用来划分命名空间,并将一批相关方法和属性组织在一起的对象,如果它可以被实例化,那么它只能被实例化一次. 单体模式,就是将代码组织为一个逻辑单元,这个逻辑单元中的代码可以通过单一的变量进行访问. 单体基本结构是这样: var Singleton = { attribute1:true, attribute2:10, method1:function(){}, method2:function(){} } 借助闭包实现单体: Namespace.Singleton = {} 定义之后立即执

5分钟读书笔记之 - 设计模式 - 组合模式

组合模式是一种专为创建Web上的动态用户界面而量身定制的模式,使用这种模式,可以用一条命令在对各对象上激发复杂的或递归的行为. 在组合对象的层次体系中有俩种类型对象:叶对象和组合对象.这是一个递归定义,但这正是组合模式如此有用的原因所在.一个组合对象由一些别的组合对象和叶对象组成,其中只有叶对象不再包含子对象,叶对象是组合对象中最基本的元素,也是各种操作的落实地点. 存在一批组织成某种层次体系的对象(具体的结构在开发期间可能无法得知) 希望这批对象或其中的一部分对象实施一个操作 表单验证实例:

5分钟读书笔记之 - 设计模式 - 工厂模式

一个类或者对象中,往往会包含别的对象.在创建这种对象的时候,你可能习惯于使用常规方式,即用 new 关键字和类构造函数. 这会导致相关的俩个类之间产生依赖. 工厂模式,就是消除这俩个类之间的依赖性的一种模式,它使用一种方法来决定究竟实例化那个具体的类. 简单工厂模式 假设你想开几个自行车商店,每个商店都有几种型号的自行车出售,可以用这样一个类来表示: var BicycleShop = function(){} BicycleShop.prototype = { sellBicycle:func

5分钟读书笔记之 - 设计模式 - 桥接模式

补充一点知识: 私有变量 在对象内部使用'var'关键字来声明,而且它只能被私有函数和特权方法访问.私有函数 在对象的构造函数里声明(或者是通过var functionName=function(){...}来定义),它能被特权函数调用(包括对象的构造函数)和私有函数调用.特权方法 通过this.methodName=function(){...}来声明而且可能被对象外部的代码调用.可以使用:this.特权函数() 方式来调用特权函数,使用 :私有函数()方式来调用私有函数.公共属性 通过thi

读书笔记之设计模式-观察者模式

行为型的设计模式 - Oberver(观察者模式) 一般常见的观察者模式如:报纸订阅. 在web应用中的电商最常见的莫过于物流接口,如快递100收费版--主动推送. 出版者+订阅者=观察者 下见2个接口, 1.Subject(主题接口) method: register() --用于注册订阅者 remove() --用于解除订阅者 notifyMessage()  --用于通知订阅者消息 2.Observer(订阅者) method: update()  --让主题接口方法notifyMessa

5分钟读书笔记之 - 设计模式 - 命令模式

本章研究的是一种封装方法调用的方式.命令模式与普通函数有所不同.它可以用来对方法调用进行参数化处理和传送,经过这样处理过的方法调用可以在任何需要的时候执行. 它也可以用来消除调用操作的对象和实现操作的对象之间的耦合.这为各种具体的类的更换带来了极大的灵活性.这种模式可以用在许多不同的场合,不过它在创建用户界面这一方面非常有用,特别是在需要不受限的取消操作的时候.它还可以用来替代回调函数,因为它能够提高在对象之间传递的操作的模块化程度. 命令的结构: 最简形式的命令对象是一个操作和用以调用这个操作