设计模式| 行为型模式 (下)

字数 2516阅读 48

前言

行为型模式,共十一种:策略模式、模板方法模式、观察者模式、迭代器模式、解释器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式。分两篇文章总结,本篇主要涉及到的设计模式是:
命令模式、备忘录模式、状态模式、访问者模式、中介者模式

其他同系列的文章还有:
面向对象编程中的六大原则
设计模式| 创建型模式
设计模式| 结构型模式
设计模式| 行为型模式 (上)
设计模式| 行为型模式 (下)
欢迎阅读,评论!!!

1、命令模式

请求发送者与接收者解耦—命令模式

在软件开发中,我们经常需要向某些对象发送请求(调用其中的某个或某些方法),
但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,
此时,我们特别希望能够以一种松耦合的方式来设计软件,使得请求发送者与请求接收者能够消除彼此之间的耦合,
让对象之间的调用关系更加灵活,可以灵活地指定请求接收者以及被请求的操作。命令模式为此类问题提供了一个较为完美的解决方案。

命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,
而不必知道如何完成请求。

   命令模式定义如下:

命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法。

 在命令模式结构图中包含如下几个角色:

 ● Command(抽象命令类):抽象命令类一般是一个抽象类或接口,在其中声明了用于执行请求的execute()等方法,
   通过这些方法可以调用请求接收者的相关操作。

 ● ConcreteCommand(具体命令类):具体命令类是抽象命令类的子类,实现了在抽象命令类中声明的方法,
   它对应具体的接收者对象,将接收者对象的动作绑定其中。在实现execute()方法时,将调用接收者对象的相关操作(Action)。

 ● Invoker(调用者):调用者即请求发送者,它通过命令对象来执行请求。一个调用者并不需要在设计时确定其接收者,
    因此它只与抽象命令类之间存在关联关系。在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,
    从而实现间接调用请求接收者的相关操作。

 ● Receiver(接收者):接收者执行与请求相关的操作,它具体实现对请求的业务处理。

命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开

每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。

命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的

命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。

在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,
从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。

2、备忘录模式

撤销功能的实现—备忘录模式

我们在编程的时候,经常需要保存对象的中间状态,当需要的时候,可以恢复到这个状态。
比如,我们使用Eclipse进行编程时,假如编写失误(例如不小心误删除了几行代码),
我们希望返回删除前的状态,便可以使用Ctrl+Z来进行返回。这时我们便可以使用备忘录模式来实现。

备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,
当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,
当前很多软件都提供了撤销(Undo)操作,其中就使用了备忘录模式。

  备忘录模式定义如下:

备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。

在备忘录模式结构图中包含如下几个角色:

● Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,
  也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。

● Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,
  根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,
  原发器的设计在不同的编程语言中实现机制会有所不同。

● Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。
  在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。

理解备忘录模式并不难,但关键在于如何设计备忘录类和负责人类。由于在备忘录中存储的是原发器的中间状态,因此需要防止原发器以外的其他对象访问备忘录,特别是不允许其他对象来修改备忘录。

备忘录模式的优缺点和适用场景

备忘录模式的优点有:

1.当发起人角色中的状态改变时,有可能这是个错误的改变,我们使用备忘录模式就可以把这个错误的改变还原。
2. 备份的状态是保存在发起人角色之外的,这样,发起人角色就不需要对各个备份的状态进行管理。

备忘录模式的缺点:

在实际应用中,备忘录模式都是多状态和多备份的,发起人角色的状态需要存储到备忘录对象中,对资源的消耗是比较严重的。

如果有需要提供回滚操作的需求,使用备忘录模式非常适合,比如jdbc的事务操作,文本编辑器的Ctrl+Z恢复等。

3、状态模式

处理对象的多种状态及其相互转换-状态模式

状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。

当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,
使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,
无论对于何种状态的对象,客户端都可以一致处理。

   状态模式定义如下:

状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。

在状态模式中引入了抽象状态类和具体状态类,它们是状态模式的核心,
在状态模式结构图中包含如下几个角色:

● Context(环境类):环境类又称为上下文类,它是拥有多种状态的对象。
  由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。
  在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State子类的对象。

● State(抽象状态类):它用于定义一个接口以封装与环境类的一个特定状态相关的行为,
  在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现类这些方法,由于不同状态下对象的行为可能不同,
  因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。

● ConcreteState(具体状态类):它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,
  每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

在状态模式中,我们将对象在不同状态下的行为封装到不同的状态类中,为了让系统具有更好的灵活性和可扩展性,
同时对各状态下的共有行为进行封装,我们需要对状态进行抽象,引入了抽象状态类角色

在抽象状态类的子类即具体状态类中实现了在抽象状态类中声明的业务方法,不同的具体状态类可以提供完全不同的方法实现,
在实际使用时,在一个状态类中可能包含多个业务方法,如果在具体状态类中某些业务方法的实现完全相同,
可以将这些方法移至抽象状态类,实现代码的复用。

状态模式将一个对象在不同状态下的不同行为封装在一个个状态类中,通过设置不同的状态对象可以让环境对象拥有不同的行为,而状态转换的细节对于客户端而言是透明的,方便了客户端的使用。

在实际开发中,状态模式具有较高的使用频率,在工作流和游戏开发中状态模式都得到了广泛的应用,
例如公文状态的转换、游戏中角色的升级等。
  1. 主要优点

     状态模式的主要优点如下:
    (1) 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理
        而不是分散在一个个业务方法中。
    (2) 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
    (3) 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,
        状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
    (4) 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
    
  2. 主要缺点

    状态模式的主要缺点如下:
    (1) 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
    (2) 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
    (3) 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,
        否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
    
  3. 适用场景

     在以下情况下可以考虑使用状态模式:
     (1) 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
     (2) 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,
         会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。
    

4、访问者模式

操作复杂对象结构——访问者模式

访问者模式是一种较为复杂的行为型设计模式,它包含访问者和被访问元素两个主要组成部分,
这些被访问的元素通常具有不同的类型,且不同的访问者可以对它们进行不同的访问操作。例如处方单中的各种药品信息就是被访问的元素
而划价人员和药房工作人员就是访问者。访问者模式使得用户可以在不修改现有系统的情况下扩展系统的功能,
为这些不同类型的元素增加新的操作。

在使用访问者模式时,被访问元素通常不是单独存在的,它们存储在一个集合中,这个集合被称为“对象结构”,
访问者通过遍历对象结构实现对其中存储的元素的逐个操作。

  访问者模式定义如下:

访问者模式(Visitor Pattern):提供一个作用于某对象结构中的各元素的操作表示,它使我们可以在不改变各元素的类的前提下定义作用于这些元素的新操作。访问者模式是一种对象行为型模式。

在访问者模式结构图中包含如下几个角色:

● Vistor(抽象访问者):抽象访问者为对象结构中每一个具体元素类ConcreteElement声明一个访问操作,
  从这个操作的名称或参数类型可以清楚知道需要访问的具体元素的类型,具体访问者需要实现这些操作方法,定义对这些元素的访问操作

● ConcreteVisitor(具体访问者):具体访问者实现了每个由抽象访问者声明的操作,每一个操作用于访问对象结构中一种类型的元素

● Element(抽象元素):抽象元素一般是抽象类或者接口,它定义一个accept()方法,该方法通常以一个抽象访问者作为参数。

● ConcreteElement(具体元素):具体元素实现了accept()方法,在accept()方法中调用访问者的访问方法以便完成对
  一个元素的操作。

● ObjectStructure(对象结构):对象结构是一个元素的集合,它用于存放元素对象,并且提供了遍历其内部元素的方法。
  它可以结合组合模式来实现,也可以是一个简单的集合对象,如一个List对象或一个Set对象。

访问者模式中对象结构存储了不同类型的元素对象,以供不同访问者访问。访问者模式包括两个层次结构,
一个是访问者层次结构,提供了抽象访问者和具体访问者,一个是元素层次结构,提供了抽象元素和具体元素。
相同的访问者可以以不同的方式访问不同的元素,相同的元素可以接受不同访问者以不同访问方式访问。
在访问者模式中,增加新的访问者无须修改原有系统,系统具有较好的可扩展性。

 在访问者模式中,抽象访问者定义了访问元素对象的方法,通常为每一种类型的元素对象都提供一个访问方法,
而具体访问者可以实现这些访问方法。这些访问方法的命名一般有两种方式:一种是直接在方法名中标明待访问元素对象的具体类型,
如visitElementA(ElementA elementA),还有一种是统一取名为visit(),通过参数类型的不同来定义一系列重载的visit()方法。
当然,如果所有的访问者对某一类型的元素的访问操作都相同,则可以将操作代码移到抽象访问者类中,

由于访问者模式的使用条件较为苛刻,本身结构也较为复杂,因此在实际应用中使用频率不是特别高。
当系统中存在一个较为复杂的对象结构,且不同访问者对其所采取的操作也不相同时,可以考虑使用访问者模式进行设计。
在XML文档解析、编译器的设计、复杂集合对象的处理等领域访问者模式得到了一定的应用。

1.主要优点

  访问者模式的主要优点如下:

(1) 增加新的访问操作很方便。使用访问者模式,增加新的访问操作就意味着增加一个新的具体访问者类,
    实现简单,无须修改源代码,符合“开闭原则”。
(2) 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。类的职责更加清晰,
    有利于对象结构中元素对象的复用,相同的对象结构可以供多个不同的访问者访问。
(3) 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

2.主要缺点

  访问者模式的主要缺点如下:

(1) 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,
    并在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”的要求。
(2) 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,
    这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。

3.适用场景

  在以下情况下可以考虑使用访问者模式:
(1) 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。
  在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
(2) 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作“污染”这些对象的类,
   也不希望在增加新操作时修改这些类。访问者模式使得我们可以将相关的访问操作集中起来定义在访问者类中,
   对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
(3) 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。

5、中介者模式

协调多个对象之间的交互—中介者模式

如果在一个系统中对象之间的联系呈现为网状结构,如下图所示。
对象之间存在大量的多对多联系,将导致系统非常复杂,这些对象既会影响别的对象,也会被别的对象所影响,这些对象称为同事对象,
它们之间通过彼此的相互作用实现系统的行为。在网状结构中,几乎每个对象都需要与其他对象发生相互作用,
而这种相互作用表现为一个对象与另外一个对象的直接耦合,这将导致一个过度耦合的系统。
对象之间存在复杂关系的网状结构
中介者模式可以使对象之间的关系数量急剧减少,通过引入中介者对象,
可以将系统的网状结构变成以中介者为中心的星形结构,如下图所示。
在这个星形结构中,同事对象不再直接与另一个对象联系,它通过中介者对象与另一个对象发生相互作用。
中介者对象的存在保证了对象结构上的稳定,也就是说,系统的结构不会因为新对象的引入带来大量的修改工作。
中介者对象的星型结构
如果在一个系统中对象之间存在多对多的相互关系,我们可以将对象之间的一些交互行为从各个对象中分离出来,
并集中封装在一个中介者对象中,并由该中介者进行统一协调,这样对象之间多对多的复杂关系就转化为相对简单的一对多关系。
通过引入中介者来简化对象之间的复杂交互,

中介者模式是“迪米特法则”的一个典型应用

中介者模式定义如下:

中介者模式(Mediator Pattern):用一个中介对象(中介者)来封装一系列的对象交互,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。中介者模式又称为调停者模式,它是一种对象行为型模式。

 在中介者模式中,我们引入了用于协调其他对象/类之间相互调用的中介者类,
为了让系统具有更好的灵活性和可扩展性,通常还提供了抽象中介者

在中介者模式结构图中包含如下几个角色:

● Mediator(抽象中介者):它定义一个接口,该接口用于与各同事对象之间进行通信。
● ConcreteMediator(具体中介者):它是抽象中介者的子类,通过协调各个同事对象来实现协作行为,它维持了对各个同事对象的引用
● Colleague(抽象同事类):它定义各个同事类公有的方法,并声明了一些抽象方法来供子类实现,
  同时它维持了一个对抽象中介者类的引用,其子类可以通过该引用来与中介者通信。
● ConcreteColleague(具体同事类):它是抽象同事类的子类;每一个同事对象在需要和其他同事对象通信时,先与中介者通信,
  通过中介者来间接完成与其他同事类的通信;在具体同事类中实现了在抽象同事类中声明的抽象方法。

  中介者模式的核心在于中介者类的引入,在中介者模式中,中介者类承担了两方面的职责:
(1) 中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,
    当需要和其他同事进行通信时,可通过中介者来实现间接调用。该中转作用属于中介者在结构上的支持。
(2) 协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致的和中介者进行交互,
    而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,
    将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持。

中介者模式将一个网状的系统结构变成一个以中介者对象为中心的星形结构,在这个星型结构中,使用中介者对象与其他对象的一对多关系来取代原有对象之间的多对多关系。中介者模式在事件驱动类软件中应用较为广泛,特别是基于GUI(Graphical User Interface,图形用户界面)的应用软件,此外,在类与类之间存在错综复杂的关联关系的系统中,中介者模式都能得到较好的应用。

  1. 主要优点

    中介者模式的主要优点如下:
    (1) 中介者模式简化了对象之间的交互,它用中介者和同事的一对多交互代替了原来同事之间的多对多交互,
       一对多关系更容易理解、维护和扩展,将原本难以理解的网状结构转换成相对简单的星型结构。
    (2) 中介者模式可将各同事对象解耦。中介者有利于各同事之间的松耦合,我们可以独立的改变和复用每一个同事和中介者,
       增加新的中介者和新的同事类都比较方便,更好地符合“开闭原则”。
    (3) 可以减少子类生成,中介者将原本分布于多个对象间的行为集中在一起,改变这些行为只需生成新的中介者子类即可,
       这使各个同事类可被重用,无须对同事类进行扩展。
    
  2. 主要缺点

     中介者模式的主要缺点如下:
     在具体中介者类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
    
  3. 适用场景

    在以下情况下可以考虑使用中介者模式:
    (1) 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
    (2) 一个对象由于引用了其他很多对象并且直接和这些对象通信,导致难以复用该对象。
    (3) 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,
       在中介者中定义对象交互的公共行为,如果需要改变行为则可以增加新的具体中介者类。
    

持续更新中......

推荐阅读更多精彩内容