设计模式之行为型模式

  第一篇:设计模式之创建型模式

  第二篇:设计模式之结构型模式

 

  在这部分里,我们关注GoF设计模式中的行为型模式,它是用来在不同对象之间划分职责和算法的抽象,行为模式不仅涉及到类和对象,还涉及到类与对象之间如何进行关联。

  行为型模式包括:职责链(Chain of Responsibility)、命令(Command)、解释器(Interperter)、迭代(Iterator)、中介者(Mediator)、备忘录(Memento)、观察者(Observer)、状态(State)、策略(Strategy)、模板(Template)和访问者(Visitor)。我们主要讨论其中的一部分模式,后续会有其他补充。

  1) 职责链(Chain of Responsibility),如果完成一项业务,需要很多步相关操作,但是如果将这些操作完全封装到一个类或者方法里面,又违背了单一职责的原则。这时我们可以考虑使用职责链模式,对应的UML图如下:

  

  我们可以创建很多个Handler的实现类,并通过设置Successor来将这些Handler“串”在一起。那么如何触发所有的Handler呢?这里和Decorator有点儿类似,我们可以通过调用Successor.HandlerRequest来实现。这样用户只需要关心最开始的Handler,而不必关心后面都还有哪些其他的Handler。

  2)命令(Command),命令模式将发出命令和执行命令很好的区分开来,当我们执行某项业务时,客户端只需要构造一个请求,而不必关心业务实现的具体细节,即构造请求和业务实现是独立的。对应的UML图如下:

  

  从图中,我们可以看到,当Client端需要执行某项业务时,它需要构造一个Invoker对象,它负责发出请求,会生成一个Command对象。同时我们看到有一个Receiver对象,它是用来实现具体业务的,我们在ConcreteCommand中,会引用这个对象,来完成具体业务。

  3)观察者(Observer),当我们的系统中,存在一个业务A,有其他多个业务都需要关注业务A,当它的状态发生变化时,其他业务都需要做出相应操作,这时我们可以使用观察者模式。观察者模式也称作订阅模式,它会定义一个“主题”(业务A),一个抽象的“订阅者”以及很多具体的“订阅者”(其他业务),在“主题”中,会保留所有“订阅者”的引用,同时可以对“订阅者”进行添加或者删除,当“主题”的状态发生变化时,它会主动“通知”所有“订阅者”,从而“订阅者”可以做出相应的操作。对应的UML图如下:

  

  我们可以看到ConcreteSubject中保留了多个Subscriber的引用(Subscribers),在NotifySubscriber方法中,它会依次调用每个Subscriber的Update方法,从而更新“订阅者”的状态。

  4)访问者(Visitor),当我们有一个对象集合,集合中的元素类型是不一样的,但类型是相对固定的,例如只有3种不同的类型,但是可能有30个元素。如果我们希望对集合中的所有元素进行某种操作,从接口的角度来看,由于类型不一致,我们很难通过一个统一的接口来遍历集合元素并对其进行操作。这时我们可以考虑使用访问者模式,它将获取某个元素和对元素进行操作进行了分离。对应的UML图如下:

  

  这里我们假设集合中只包括了2中不同的类型,ObjectBuilder就是上面提到的集合,它包含多个不同的IElement元素,业务的核心实现是在VisitorA和VisitorB中,对于Element1的Accept方法来说,它只是调用visitor.VisitElement1方法。

  5)模板(Template),继承是面向对象的一大核心,而模板方法就是对继承的完美体现。对于某项业务来说,我们可以根据通用的流程,设计其方法骨架,针对不清晰或者不明确的地方,以抽象方法的方式来处理,然后根据不同的子业务,创建不同的子类,在子类中,实现那些抽象方法。对应的UML图如下:

  

  可以看出,对于子类来说,它是不需要重写Operate方法的,而只需要实现父类的抽象方法。对于客户端来说,当它实例化某个子类后,可以直接调用Operate方法来完成某项业务。

  6)策略(Strategy),当我们的系统中,针对某项业务有多个算法时,如何对这些算法进行管理,我们可以考虑使用策略模式,它主要是针对一组可以提取相同接口的算法进行管理。对应的UML图如下:

  

  这里需要注意的是,Strategy类并不知道应该使用哪个具体的子类,这应该由Client指定。

  7)解释器(Interperter),如果我们的系统中有些特定的问题反复出现,我们想要对这些问题进行抽象,那应该如何做?试想一下,当我们写完代码后,是如何进行编译的?无论对C#还是Java,它们的编译器都会读取我们所写的每一行代码,并作出相应的解释。我们可以部分认为,编译器中存储了任何组合的语句,类似于C中的typedef。解释器做的就是类似的事情,它将具有通用性的问题进行抽取,对其解决方案进行综合处理。对应的UML图如下:

  

  一般的执行过程是这样的,Client读取Context中的信息,根据某种原则将其划分成多个部分,针对每一部分,构造相应的解释器,并将Context信息传入解释器中进行处理。这里的问题是Client必须要清楚Context细节和具体解释器中间的关联。我们可以在Client和Interpreter之间构造一个“解释器工厂”,用来根据Context生成相应的解释器实例,同样,如果解释器的执行过程和数据无关,我们可以为“解释器工厂”上追加“单例”模式,构造一个解释器池。这些都是可以根据需求做的进一步的优化。

  8)迭代(Iterator),前文提到的访问者(Visitor)模式,针对的是存储在一起的不同类型的对象集合,如何进行遍历处理,那么针对存储在一起的相同类型的对象集合,我们应该如何进行遍历呢?迭代模式可以帮我们做到,对应的UML图如下:

  

  在C#和Java中,我们都已经在语言层级上实现了迭代,例如C#中的foreach,同时.NET来设计了两个接口来实现迭代:IEnumerator和IEnumerable。

  9)中介者(Mediator),如果我们的系统中有多个对象,彼此之间都有联系,那这是一个对象之间耦合很高的系统,我们应该如何优化呢?我们可以建立一个知道所有对象的“对象”,在它内部维护其他对象之间的关联,这就是中介者模式,对应的UML图如下:

  

  这里,Mediator是知道所有IPerson的“底细”的,Client可以直接与Mediator联系,而不必关心具体的是PersonA还是PersonB,同样,对于PersonA和PersonB来说,它们之间也没有直接联系,当两者需要通信时,之金额使用Mediator的Send方法。

  这个模式不好的地方在于:1)所有的IPerson类型都要有Mediator引用,这样才能和其他的Person通信;2)Mediator需要维护所有Person的实例,这样它才能做出正确的判断,将消息发给对应的Person,但当Person子类过多时,Mediator就变的较难维护,这时,我们可以创建一套关于产生Person实例的“工厂”,会减轻Mediator的负担。

  10)备忘录(Memento),当我们的系统中存在这样一种对象,它的属性很多,在某些情况下,它的一部分属性是需要进行备份和恢复的,那应该如何做?谈到备份和恢复,我们立刻想到可以使用原型模式,但那是针对所有属性的,备忘录模式可以很好地解决这里的问题,对应的UML图如下:

  

  在这里,我们希望Originator的State2、State3是可以备份和恢复的,其他属性是无关的。我们可以在希望备份Originator的地方,调用Creatememento方法,在希望恢复Originator部分属性的地方,调用RestoreMemento方法,同时MementoManager对Memento进行管理。

  11)状态(State),当我们的系统中的对象,需要根据传入的不同参数,进行不同的处理,而且传入参数的种类特别多,这时在方法内部会产生大量的if语句,来确定方法的执行分支。那么如何消除这些if语句呢?状态模式可以帮我们做到,对应的UML图如下:

  

  这里,Client只与Context关联,在Context内部,会维护不同状态之间的跳转,简单来说,就是在HandleRequest内部判断传入的state值,如果符合业务逻辑,那么直接调用state的HandleRequest方法;如果不符合,那么修改state值,然后调用相应state的HandleRequest方法。

posted @ 2013-05-02 11:31  李潘  阅读(4117)  评论(2编辑  收藏  举报