行为型设计模式

在软件系统运行时,对象将不是孤立存在的,它们可以通过相互通信协作完成某些功能,一个对象在运行时也将影响到其它对象的运行。行为型模式(Behavioral Pattern)关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责。行为型模式不仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分

行为型设计模式包括职责链模式(Chain of Responsibility Pattern)、命令模式(Command Pattern)、解释器模式(Interpreter Pattern)、迭代器模式(Iterator Pattern)、中介者模式(Mediator Pattern)、备忘录模式(Memento Pattern)、观察者模式(Observer Pattern)、状态模式(State Pattern)、策略模式(Strategy Pattern)、模板方法模式(Template Method Pattern)和访问者模式(Visitor Pattern)。

职责链模式

动机

专门用来处理请求链式传递的模式

概念

职责链模式(Chain Of Responsibility Pattern):避免将请求发送者与接受者耦合在一起,让多个对象都有机会接受请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
设计模式分类

优缺点

优点:

  • 职责链模式使得一个对象无须知道是其它哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接受者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。
  • 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
  • 在给对象分派职责时,职责链可以提供更多的灵活性,可以通过在运行时对链进行动态的增加或修改来增加或改变处理一个请求的职责。
  • 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合开闭原则的。

缺点:

  • 由于一个请求没有明确的接受者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
  • 对于比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定的影响,而且在进行代码调试时不太方便。
  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
适用场景
  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求带运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
  • 在不明确指定接受者的情况下,向多个对象中的一个提交一个请求。
  • 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。

命令模式

动机

为了降低系统的耦合度,将请求的发送者和接收者解耦,可以使用一种被称为命令模式的设计模式来设计系统。在命令模式中,发送者与接收者之间引入了新的命令对象,将发送者的请求封装在命令对象中,再通过命令对象来调用接收者的方法。

概念

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

优缺点

优点:

  • 降低系统的耦合度。请求者和接收者直接不存在直接引用,一个请求者可以对应不同的接收者使用,一个接收者也可以对应不同的请求者。
  • 修改容易,新增加的具体命令类不会影响到其它类,无须修改原有系统源代码甚至客户类代码,满足开闭原则。
  • 可以比较容易地设计一个命令队列或宏命令(组合命令)。
  • 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计实现方案

缺点:

  • 因为可能存在过多的具体命令类导致系统中存类的个数增多
适用场景
  • 系统需要将调用者和接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
  • 系统需要支持命令(Undo)操作和恢复(Redo)操作。
  • 系统需要将一组操作组合在一起形成宏命令。

解释器模式

动机

将一套基于一定规则定义的语句用现有的编程语言解释成可操作的语言(个人理解)

概念

解释器模式(Interpreter Pattern): 定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。
设计模式分类

优缺点

优点:

  • 易于改变和扩展方法。由于在解释器模式中使用类来表示语言的分发规则,因此可以通过继承等机制来改变或扩展方法。
  • 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
  • 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别负责,还可以通过一些工具自动生成节点类代码。
  • 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式,原有表达式类代码无须修改,符合开闭原则。

缺点:

  • 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
  • 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
适用场景
  • 可以将一个需要解释执行的语言中的句子表示为一个抽象语法数。
  • 一些重复出现的问题可以用一种简单的语言来进行表达。
  • 一个语言的文法较为简单。
  • 执行效率不是关键问题。(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换为其它形式,使用解释器模式的执行效率并不高。)

迭代器模式

动机

如果我们就电视机看成一个存储电视频道的集合对象,通过遥控器可以对电视中的电视频道集合进行操作,例如返回上一个频道、跳转到下一个频道或者跳转至指定的频道。遥控器为操作电视频道带来很大方便,用户并不需要知道这些频道到底如何存储在电视机中。在软件开发中,也存在大量类似电视机一样的类,它们可以存储多个成员对象(元素),这些类通常称为聚合类(Aggregate Classes),对应的对象称为聚合对象。为了更加方便地操作这些聚合对象,同时可以很灵活地为聚合对象增加不同的遍历方法,也需要类似电视机遥控器一样的角色,可以访问一个聚合对象中的元素但又不需要暴露它的内部结构。通过引入迭代器,客户端无须了解对象的内部结构即可实现对聚合对象中成员的遍历,还可以根据需要很方便地增加新的遍历方式。

概念

迭代器模式(Iterator Pattern): 提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为为游标(Cursor)。
设计模式分类

优缺点

优点:

  • 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,也可以自己定义迭代器的子类以支持新的遍历方式。
  • 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样就可以简化聚合类的设计。
  • 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足开闭原则的要求。

缺点:

  • 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
  • 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无用用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。
适用场景
  • 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
  • 需要为一个聚合对象提供多种遍历方式。
  • 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口。

中介者模式

动机

在有些软件中,一些类/对象之间的相互调用关系错综复杂,类似QQ用户之间的关系,此时,特别需要一个类似“QQ群”一样的中间类来协调这些类/对象之间的复杂关系,以降低系统的耦合度。中介者模式正好可以解决此类问题。

概念

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

优缺点

优点:

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

缺点:

  • 在具体中介者类中包含了大量同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。
适用场景
  • 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
  • 一个对象由于引用了其它很多对象并且直接和这些对象通信,导致难以复用该对象。
  • 想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者定义对象交互的公共行为,如果需要改变行为则可以增加新的具体中介者类。

备忘录模式

动机

用来在软件设计中实现回退机制

概念

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

优缺点

优点:

  • 它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
  • 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其它代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。

缺点:

  • 资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免地需要占用大量的存储空间,每保存一次对象的状态的状态都需要消耗一定的系统资源。
适用场景
  • 保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时就能够恢复到先前的状态,实现撤销操作。
  • 防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。

观察者模式

动机

在软件系统中,有些对象之间也存在类似交通信号灯和汽车之间的关系,一个对象的状态或行为变化将导致其他对象的状态或行为也发生改变,它们之间将产生联动。为了更好地描述对象之间存在的这种一对多(包括一对一)的联动,观察者模式应用而生,它定义了对象之间一种一对多的依赖关系,让一个对象的改变能够影响其它对象。

概念

观察者模式(Observer Pattern): 定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish-Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source-Listener)模式或从属者(Dependents)模式。
设计模式分类

优缺点

优点:

  • 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
  • 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
  • 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
  • 观察者模式满足开闭原则的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

缺点:

  • 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象时怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使用它们可以各自独立地改变和复用。
  • 一个对象的改变将导致一个或多个其它对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响到C对象······,可以使用观察者模式创建一种链式触发机制。

状态模式

动机

在软件系统中,有些对象可以具有多种状态,这些状态再某些情况下能够相互转换,而且对象在不同的状态下也将具有不同的行为。为了更好地对这些具有多种状态的对象进行相互设计,可以使用一种被称之为状态模式的设计模式。

概念

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

优缺点

优点:

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

缺点:

  • 状态模式的使用必然会增加系统类和对象的个数,导致系统运行开销增大。
  • 状态模式的程序结构与实现都较为复杂,如果使用不当讲导致程序结构和代码的混乱,增加系统设计的难度。
  • 状态模式对开闭原则的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需要修改对应类的源代码。
适用场景
  • 对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化。
  • 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。

策略模式

动机

在软件开发中,实现某一个功能有多条途径,每一条途径对应一种算法,此时可以使用一种设计模式来灵活地选择解决途径,也能够方便地增加新的解决途径。策略模式就是这样一种为了适应算法灵活性而产生的设计模式。

概念

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化,也称为政策模式(Policy)。
设计模式分类

优缺点

优点:

  • 符合开闭原则,修改方便。
  • 切当使用继承可以将公共代码移到抽象策略类中,从而避免重复的代码。
  • 使用策略模式可以避免多重条件选择语句.
  • 策略模式提供了一种算法复用机制,由于将算法单独提取出来封装在策略类中,因此不同的环境类可以方便地复用这些策略类.

缺点:

  • 客户端必须知道所有的策略类,并自行决定使用哪一个策略类.
  • 策略模式将造成系统产生很多具体策略类,任何细小的变化都将导致系统要增加一个新的具体策略类.
适用场景
  • 不希望客户端知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关的数据结构,可以提高算法的保密性与安全性。
  • 一个对象有很多的行为,如果不用切当的模式,这些行为就只好使用多重条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以使用难以维护的多重条件选择语句。

模板方法模式

动机

保证逻辑顺序的情况下,利用继承完成现有功能。

概念

模板方法模式(Template Method Pattern):定义一个操作中算法的框架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
设计模式分类

优缺点

优点:

  • 模板方法模式可实现一种反向控制结构,通过子类覆盖父类的钩子方法来决定某一个特定步骤是否需要执行。

缺点:

  • 需要为每一个基本方法的不同实现提供一个子类,如果父类中可变的基本方法太多,将会导致类的个数增加,系统更加庞大,设计也更加抽象,此时,可以结合桥接模式来进行设计。
适用场景
  • 对一些复杂的算法进行分割,将其算法中固定不变的部分设计为模板方法和父类具体方法,而一些可以改变的细节由其子类来实现。及一次性地实现一个算法的不变部分,并将可变的行为留给子类来实现。
  • 各子类中公共的行为应被提取出来集中到一个公共父类中以避免代码重复。
  • 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。

访问者模式

动机

以不同的方式操作复杂对象结构,将数据结构与数据操作分离。

概念

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

优缺点

优点:

  • 增加新的访问操作方便。
  • 将有关元素对象的访问行为集中到一个访问者对象中,而不是分散在一个个的元素类中。
  • 让用户能够在不修改现有元素类层次结构的情况下,定义作用于该层次结构的操作。

缺点:

  • 增加新的元素类困难。每增加一个新的元素类都意味着要在抽象访问者角色中增加一个新的抽象操作,并在每一个具体访问者类中增加相应的具体操作。
  • 破坏封装。访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部操作和内部状态,否则无法供访问者访问。
适用场景
  • 一个对象结构包含多个类型的对象,希望对这些对象实施一些依赖其具体类型的操作。在访问者中针对每一种具体的类型都提供了一个访问操作,不同类型的对象可以有不同的访问操作。
  • 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而且需要避免让这些操作“污染”这些对象的类,也并不希望在增加新操作时修改这些类。访问者模式将相关的访问操作集中起来定义在访问者类中,对象结构可以被多个不同的访问者类所使用,将对象本身与对象的访问操作分离。
  • 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。