设计模式备忘 - 行为型
责任链模式(Chain of Responsibility)
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。从定义上可以看出,责任链模式的提出是为了“解耦”,以应变系统需求的变更和不明确性。
?
该模式又包含两种处理思想。纯的责任链模式,规定一个具体处理者角色只能对请求作出两种动作:自己处理;传给
下家。不能出现处理了一部分,把剩下的传给了下家的情况。而且请求在责任链中必须被处理,而不能出现无果而终的结局。反之,则就是不纯的责任链模式。具体由两个角色组成:
抽象处理者角色(Handler):它定义了一个处理请求的接口。当然对于链子的不同实现,也可以在这个角色中实现后继链。具体处理者角色(Concrete Handler):实现抽象角色中定义的接口,并处理它所负责的请求。如果不能处理则访问它的后继者。适用范围:
一个较佳的例子就是Java的例外处理机制,当程式中发生例外时,也比会catch所捕捉的例外是否符合,如果符合就执行所设定的处理,如果都没有比对到适当的例外物件,就会将例外丢出try...catch区块之外。另一简单例子如下:
?
该模式有待进一步研究。
?
迭代器模式(Iterator)迭代器(Iterator)模式,又叫做游标(Cursor)模式。GOF 给出的定义为:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节。该模式由以下角色组成:
迭代器角色(Iterator):负责定义访问和遍历元素的接口。具体迭代器角色(Concrete Iterator):实现迭代器接口,并要记录遍历中的当前位置。容器角色(Container):负责提供创建具体迭代器角色的接口。具体容器角色(Concrete Container):实现创建具体迭代器角色的接口。我们来看下Java Collection中的迭代器是怎么实现的吧。?第三种方式是不太推荐使用的:使用clone方法来简化备忘录模式。由于Java提供了clone机制,这使得复制一个对象变得轻松起来。使用了clone机制的备忘录模式,备忘录角色基本可以省略了,而且可以很好的保持对象的封装。但是在为你的类实现clone方法时一定要慎重。
GOF 在《设计模式》中总结了使用备忘录模式的前提:必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
观察者模式(Observer)观察者(Observer)模式又名发布-订阅(Publish/Subscribe)模式。GOF 给观察者模式如下定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
?
该模式由以下部分组成:
抽象目标角色(Subject):目标角色知道它的观察者,可以有任意多个观察者观察同一个目标。并且提供注册和删除观察者对象的接口。目标角色往往由抽象类或者接口来实现。抽象观察者角色(Observer):为那些在目标发生改变时需要获得通知的对象定义一个更新接口。抽象观察者角色主要由抽象类或者接口来实现。具体目标角色(Concrete Subject):将有关状态存入各个Concrete Observer对象。当它的状态发生改变时,向它的各个观察者发出通知。具体观察者角色(Concrete Observer):存储有关状态,这些状态应与目标的状态保持一致。实现Observer的更新接口以使自身状态与目标的状态保持一致。在本角色内也可以维护一个指向Concrete Subject对象的引用。观察者模式在关于目标角色、观察者角色通信的具体实现中,有两个版本:
一种情况便是目标角色在发生变化后,仅仅告诉观察者角色“我变化了”,观察者角色如果想要知道具体的变化细节,则就要自己从目标角色的接口中得到。这种模式被很形象的称为:拉模式 - 就是说变化的信息是观察者角色主动从目标角色中“拉”出来的。还有一种方法,那就是目标角色“服务一条龙”,通知你发生变化的同时,通过一个参数将变化的细节传递到观察者角色中去。这就是“推模式” - 管你要不要,先给你。这两种模式的使用,取决于系统设计时的需要。如果目标角色比较复杂,并且观察者角色进行更新时必须得到一些具体变化的信息,则“推模式”比较合适。如果目标角色比较简单,则“拉模式”就很合适。
适用范围:
当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变。当一个对象必须通知其它对象,而它又不能假定其它对象是谁。换言之,你不希望这些对象是紧密耦合的。?
策略模式(Strategy)策略模式(Strategy)主要是定义一系列的算法,把这些算法一个个封装成拥有共同接口的单独的类,并且使它们之间可以互换。策略模式使这些算法在客户端调用它们的时候能够互不影响的变化。这里的算法不要狭义的理解为数据结构中的算法,可以理解为不同的业务处理方法。
?
先由定义来想象下它的结构吧。要使算法拥有共同的接口,这样就要实现一个接口或者一个抽象类出来才行:
算法使用环境(Context)角色:算法被引用到这里和一些其它的与环境有关的操作一起来完成任务。抽象策略(Strategy)角色:规定了所有具体策略角色所需的接口。在Java它通常由接口或者抽象类来实现。具体策略(Concrete Strategy)角色:实现了抽象策略角色定义的接口。使用建议:系统需要能够在几种算法中快速的切换。系统中有一些类它们仅行为不同时,可以考虑采用策略模式来进行重构。系统中存在多重条件选择语句时,可以考虑采用策略模式来重构。但是要注意一点,策略模式中不可以同时使用多于一个的算法。
状态模式(State)GOF《设计模式》中给状态模式下的定义为:允许一个对象在其内部状态改变时改变它的行为。
?
能够让程序根据不同的外部情况来做出不同的响应,最直接的方法就是在程序中将这些可能发生的外部情况全部考虑到,使用if else语句来进行代码响应选择。但是这种方法对于复杂一点的状态判断,就会显得杂乱无章,容易产生错误。而且增加一个新的状态将会带来大量的修改。这个时候“能够修改自身”的状态模式的引入也许是个不错的主意。
?
由简单的开始会比较好理解状态模式的作用,先来看一个例子,如果您有一个只能顺时针转动的瓦斯开关,转动一次的状态为off、 small fire、medium fire与large fire,您如何在程序中控制状态的变化与行为呢?一个最简单的方式就是用if..else或是switch流程来控制,例如:
?
?
这个方法很简单,每个人都会,但如果您的状态变化并不是流水式的变化,而是像TCP连线状态一样,会是一个网络图的时候,用 if...else或switch来写的话,您的程序就会乱的不像话了;来考虑如何让物件控制自己的状态转换与所应表现的行为,这个程序可以这样改写:
?
?
适用情况:
一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。状态 VS 策略:
Brandon Goldfedder 在《模式的乐趣》里是这么说的:“Strategy模式在结构上与State模式非常相似,但是在概念上,他们的目的差异非常大。区分这两个模式的关键是看行为是由状态驱动还是由一组算法驱动,这条规则似乎有点随意,但是在判断时还是需要考虑它。通常,State模式的“状态”是在对象内部的,Strategy模式的“策略”可以在对象外部,不过这也不是一条严格、可靠的规则。”
策略模式中,算法是否变化完全是由客户程序决定的,而且往往一次只能选择一种算法,不存在算法中途发生变化的情况。而状态模式如定义中所言,在它的生命周期中存在着状态的转变和行为得更改,而且状态变化是一个线形的整体,对于客户程序来言,这种状态变化往往是透明的。
模板模式(Template Method)模板方法(Template Method)模式定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。可以看出来,模板方法模式也是为了巧妙解决变化对系统带来的影响而设计的。使用模板方法使系统扩展性增强,最小化了变化对系统的影响。
这个模式中仅仅使用到了继承关系,父类中定义一些抽象方法,再有子类去实现它们。
适用情况:一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。控制子类扩展,模板方法只在特定点调用操作,这样就只允许在这些点进行扩展。
访问者模式(Visitor)《设计模式》一书对于访问者模式给出的定义为:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于Java中的Collection概念了。
以下是访问者模式的组成结构:访问者角色(Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。具体访问者角色(Concrete Visitor):实现每个由访问者角色(Visitor)声明的操作。元素角色(Element):定义一个Accept操作,它以一个访问者为参数。具体元素角色(Concrete Element):实现由元素角色提供的 Accept 操作。对象结构角色(Object Structure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。假设有一个集合,里面不仅储存一种类型的元素,如果想要对这些元素作出一些个别化的操作,首要条件就是要知道该元素的类型,使用instanceof似乎是个不错的方式,在程序简单的情况下,我们可以这么作:?这么作并不是不可以,只是将来的扩充性不大,如果想要一次改变对每一种类型元素的操作,必须修改很多地方。从元素自身的角度来想好了,元素在一个个的房子中,元素说:“不要在房子外费尽心思判断了,即然您不知道我是谁,那么您就进来访问我好了,我告诉您我是谁,这么一来您就知道如何操作我了!”于是我们用访问者模式来重构一下上面的代码:public interface IElement { public void accept(IVisitor visitor); } public class ElementA implements IElement { public void accept(IVisitor visitor) { visitor.visit(this); } public void operationA() { System.out.println("do A's job...."); } } public class ElementB implements IElement { public void accept(IVisitor visitor) { visitor.visit(this); } public void operationB() { System.out.println("do B's job...."); }} public class ElementC implements IElement { public void accept(IVisitor visitor) { visitor.visit(this); } public void operationC() { System.out.println("do C's job...."); } } public interface IVisitor { public void visit(ElementA element); public void visit(ElementB element); public void visit(ElementC element); } public class VisitorA implements IVisitor { public void visit(ElementA element) { element.operationA(); } public void visit(ElementB element) { element.operationB(); } public void visit(ElementC element) { element.operationC(); } } public class Main { public static void main(String[] args) { // know nothing about their type // after storing them into Element array IElement[] list = {new ElementA(), new ElementB(), new ElementC()}; IVisitor visitor = new VisitorA(); for (int i=0; i < list.length; i++) list[i].accept(visitor); } }?双重分派:
你在上面的例子中体会到双重分派的实现了没有?
首先在客户程序中将具体访问者模式作为参数传递给具体元素角色,这便完成了一次分派。进入具体元素角色后,具体元素角色调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去。具体访问者模式再根据参数的不同来选择方法来执行。这便完成了第二次分派。
适用情况:一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。