JavaScript设计模式笔记 - 观察者模式 命令模式

发布于 2012-12-27 | 更新于 2020-09-20

1、观察者模式

观察者模式又称为发布者-订阅者模式(publisher-subscriber)。用Javascript的话来说,这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知。

观察者模式存在的两个角色:观察者和被观察者。这里我们成为发布者和订阅者。

1.1、示例:报纸的投送

发行方也是投送方(deliver)。一般说来,一个发行方很可能有许多订阅者,同样,一个订阅者也很可能会订阅多家报社的报纸。问题的关键在于,这是一种多读多的关系,需要一种高级的抽象策略,以便订阅者能够彼此独立地发生改变,而发行方能够接受任何有消费意向 的订阅者。

1.1.1、推与拉的比较

推:主动把报纸发送到订阅者的家门口。

拉:规模较小的本地报社可能会在订阅者家附近的街角提供自己的数据,供订阅者“拉”。

1.1.2、模式的实践

订阅者:可以订阅和退订。

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

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

1.2、构建观察者API

首先需要一个发布者Publisher的构造函数,其中定义了一个类型为数组的属性,用来保存订阅者的引用;

接下来所有的Publisher实例都应该能够投送数据,所以在prototype中添加deliver方法;

给予订阅者订阅的能力;

提供unsubscribe方法供订阅者停止对指定发布者的观察;

example

1.3、现实生活中的观察者

观察着模式对于那种由许多Javascript程序员合作开发的大型程序特别有用,可以提高API灵活性,并行开发的多个实现能够彼此独立的进行修改。在富用户界面应用程序中,drag、drop、movedcomplete和tabSwitch都可能是令人感兴趣的事件,它们都是在普通浏览器事件的基础上抽象出来的可观察事件,可由发布者对象向其监听者广播。

1.4、示例:动画

动画的三个时刻:开始onStart,结束onComplete和进行中onTween。

example

1.5、事件监听器也是观察者

事件处理器:是一种把事件传递给与其关联的函数的手段,而且这种模型中一种事件只能指定一个毁掉方法。

监听器模式:一个事件可以与几个监听器关联,每个监听器都能能独立于其他监听器而改变。

example

1.6、观察着模式的适用场合

如果希望把人的行为和应用程序的行为分开,那么观察者模式正适用于这种场合。你可以直接监听click事件,不过这需要知道监听的是哪个元素,这样做的另一个弊端是你的实现与click事件直接绑定在了一起。更好的做法是:创建一个可观察的onTabChange对象,并且在特定事件发生时通知所有观察着。

1.7、观察者模式之利

观察者模式是开发基于行为的大型应用程序的有力手段。你可以削减为事件注册监听器的次数,让可观察对象借助一个事件监听器替你处理各种行为并将信息委托给它的所有订阅者,从而降低了内存消耗和提高互动性能。

1.8、观察者模式之弊

使用这种观察着接口的一个不利之处在于创建可观察对象所带来的加载时间开销。这可以通过惰性加载技术加以化解。

2、命令模式

命令模式是一种用来封装单个操作(discrete action)的结构型模式。其封装的操作可能是单个方法调用这么简单,也可能是整个子程序那么复杂。经封装的操作可以作为一等的对象进行传送。命令对象主要用于消除调用者与接受者之间的耦合,这有助于创建高度模块化的调用者,它们对所调用的操作不需要任何了解。

2.1、命令的结构

下面展示一个典型的命令类StartAd和StopAd,它们的构造函数由另一个对象adObject作为参数,而它们实现的execute()方法则要调用该对象的某个方法。现在有了两个可用在用户界面中的类,它们具有相同的接口,你不需要也不关心adObject方法的具体实现,只需要知道它实现了start()和stop()方法就可以了。借助命令模式,可以实现用户界面对象与广告对象的隔离。

example

2.1.1、用闭包创建命令对象

这种方法不需要创建一个具有execute方法的对象,而是把想要执行的方法包装在闭包中。这样做省却了作用域和this关键字的绑定这方面的烦恼。

example>

这些命令函数可以像命令对象一样四处传递,并且在需要的时候执行。它们是正式的对象类的简单替代品,但并不适用于需要多个命令方法的场合,比如后面实现取消功能的那个示例。

2.1.2、客户、调用者和接受者

客户:创建命令,StartAd StopAd

调用者:执行命令,UiButton

接受者:在命令执行时执行相应操作,adObject

2.1.3、在命令模式中使用接口

可以使用接口检查命令对象是否实现了正确的执行操作。果用闭包来创建命令函数,那么这种检查更简单,只需要检查该命令是否为函数即可。

example

2.2、命令对象的类型

简单命令对象:这种情况下的命令对象所起的作用只不过是把现有接受者的操作与调用者绑定在一起。它们与客户、接受者和调用者之间只是松散地耦合在一起。

复杂命令对象:封装这一套复杂指令的命令对象,这种命令对象实际上没有接受者,因为它自己提供了操作的具体实现。

灰色地带:有些命令对象不但封装了接收者操作,而且其execute方法也具有一些实现代码。

example

简单命令对象一般用来消除两个对象(接收者和调用者)之间的耦合,而复杂命令对象则一般用来封装不可分的(atomic)或事务性(transactional)的指令。本章着重讨论简单命令对象。

2.3、示例:菜单项

example

命令模式非常适合用来构建用户界面,这是因为这种模式可以把执行具体工作的类与生成用户界面的类隔离开来。在这种模式中,甚至可以让多个用户界面元素公用同一个接受者或命令对象。

2.4、示例:取消操作和命令日志

像UndoDecorator类一样,UndoButton类的构造函数也需要把命令栈作为参数传入。这个栈其实就是一个数组。调用经UndoDecorator对象装饰过的命令对象的execute方法时这个命令对象会被压入栈。为了执行取消操作,取消按钮会从命令栈中弹出最近的命令并调用其undo方法。这将逆转刚执行过的操作。

创建命令接口和对象,使用数组保存操作,通过装饰者加入保存和获取操作命令的方法。

example

2.4.1、使用命令日志实现不可逆操作的取消

前面讨论的取消擦偶偶针对的都是移动指针这类容易逆转的操作。对于那些本质上不可逆的操作,要想实现不受限制的取消就困难的多。取消这种操作的唯一办法就是清除状态,然后把之前执行过的操作依次重做一遍。即把所有执行过的命令记录在栈中,要想取消一个操作,需要做的就是从栈中弹出最近那个命令并弃之不用,然后清理画布并从头开始重新执行记录下来的所有命令。

example

2.4.2、用于崩溃恢复的命令日志

命令日志的一个有趣用途是在程序崩溃后恢复其状态。可以用XHR把经过序列化处理的命令记录到服务器上。用户下次访问该网页的时候,系统可以找出这些命令并用其将画布上的图案精确恢复到浏览器关闭时的状态。

2.5、命令模式的适用场合

命令模式主要用途是把调用对象(用户界面、API和代理等)与实现操作的对象隔离开。最能体现其效用的还是那种需要对操作进行规范化处理的场合。有了这种规范化处理,一个类或调用者也能调用多种方法,而且不需要先为此了解哪些方法。许多用户界面元素都非常符合这样的特征,比如前面例子中的那种菜单。命令模式可以彻底消除用户界面元素与负责实际工作的类之间的耦合。

可以受益于命令模式的还有其他一些特别场合。这种模式可以用来封装用于XHR调用或其他延迟性调用场合的回调函数。用一个回调函数命令代替回调函数,可以把多条函数调用封装为一个单位。有了命令对象的帮助,在应用程序中实现取消机制几乎是一件不足挂齿的事。

2.6、命令模式之利

如果运用得当,可以提高程序的模块化程度和灵活性;

有了它,实现取消和状态恢复等复杂的有用特性非常容易。

命令对象具有的特性比普通方法引用多得多。它可以被参数化处理,而且将那些参数保存起来以供多次调用。你可以为它定义的方法不只有execute,还可以是undo等别的方法,这样一来同样的操作就可以用不同的方式执行。还可以定义与操作相关的元数据,这些元数据可用于对象内省(introspection)或事件日志等目的。命令对象是经过封装的方法调用,它因为这种封装而拥有了方法调用本身所不具备的许多特性。

2.7、命令模式之弊

如果一个命令对象只包装了一个方法调用,而且其唯一目的就是这层对象包装的话,那么这种做法是一种浪费。如果你不需要命令模式给予的任何额外特性,也不需要具有一致接口的类所带来的模块性,那么直接使用方法引用而不是完整的命令对象也许更恰当。如果命令对象是运行期间动态创建的而你又难以确定它包含着什么操作的话,情况尤其如此。命令对象都具有同样的接口并且可以所有更好这一点是把双刃剑。在调试复杂的应用程序时它们很难跟踪。

本文作者: arthinking

本文链接: https://www.itzhai.comjavascript-design-patterns-notes-observer-mode-command-mode.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。