行为型模式(Behavioral Pattern)
通过类之间不同的通信方式实现不同的通信行为

责任链模式(Chain of Responsibility Pattern)

Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the
request.Chain the receiving objects and pass the request along the chain until an object handles it.
(使多个对象都有机会处理请求,从而避免了请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。)

也叫作职责链模式,用于避免请求发送者与多个请求处理者耦合在一起,让所有请求的处理者持有下一个对象的引用,从而将请求串成一条链,在有请求时,可将请求沿这条链传递,直到遇到该对象的处理器
在责任链模式下,用户只需要将请求发送到责任链上即可,无须关心请求的处理细节和传递过程,所以责任链模式优雅地将请求的发送和处理进行了解耦

角色

抽象处理者Handler

抽象的处理者实现三个职责:

一是定义一个请求的处理方法handleMessage,唯一对外开放的方法;

二是定义一个链的编排方法setNext,设置下一个处理者;

三是定义了具体的请求者必须实现的两个方法:定义自己能够处理的级别getHandlerLevel和具体的处理任务echo

具体处理者ConcreteHandler

在处理者中涉及三个类:

  • Level类负责定义请求和处理级别
  • Request类负责封装请求
  • Response负责封装链中返回的结果

优点

责任链模式非常显著的优点是将请求和处理分开。请求者可以不用知道是谁处理的,处理者可以不用知道请求的全貌(例如在J2EE项目开发中,可以剥离出无状态Bean由责任链处理),两者解耦,提高系统的灵活性

缺点

责任链有两个非常显著的缺点:

一是性能问题,每个请求都是从链头遍历到链尾,特别是在链比较长的时候,性能是一个非常大的问题。

二是调试不很方便,特别是链条比较长,环节比较多的时候,由于采用了类似递归的方式,调试的时候逻辑可能比较复杂

命令模式(Command Pattern)

Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log
requests,and support undoable operations.(将一个请求封装成一个对象,从而让你使用不同的请求把客户端参数化,对请求排队或者记录请求日志,可以提供命令的撤销和恢复功能)

指将请求封装为命令基于事件驱动异步地执行,以实现命令的发送者和命令的执行者之间的解耦,提高命令发送、执行的效率和灵活度
命令模式将调用者与命令执行者解耦,有效降低系统的耦合度。同时,由于命令调用者和命令修改者执行了解耦,所以增加和删除(回滚)命令变得十分方便

主要角色

Command命令角色

需要执行的所有命令都在这里声明,定义执行命令的抽象方法execute()

Receive接收者角色

该角色就是干活的角色,命令传递到这里是应该被执行的,定义了命令执行的具体方法action()

Invoker调用者角色

接收到命令,并执行命令

优点

  • 类间解耦

调用者角色与接收者角色之间没有任何依赖关系,调用者实现功能时只需调用Command抽象类的execute方法就可以,不需要了解到底是哪个接收者执行

  • 可扩展性

Command的子类可以非常容易地扩展,而调用者Invoker和高层次的模块Client不产生严重的代码耦合

  • 命令模式结合其他模式会更优秀

命令模式可以结合责任链模式,实现命令族解析任务;结合模板方法模式,则可以减少Command子类的膨胀问题

反悔问题 有两种解决方法

  • 一是结合备忘录模式还原最后状态,该方法适合接收者为状态的变更情况,而不适合事件处理;
  • 二是通过增加一个新的命令,实现事件的回滚

解释器模式(Interpreter Pattern)

Given a language, define a representation for its grammar along with an interpreter that uses the representation to
interpret sentences in the language.(给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子)

这种模式常被用于SQL解析、符号处理引擎等

角色

抽象解释器(Abstract Expression)

定义解释器的接口,约定解释器所包含的操作,比如interpret()方法 具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和Non-terminalExpression完成
抽象表达式是生成语法集合(也叫做语法树)的关键,每个语法集合完成指定语法解析任务,它是通过递归调用的方式,最终由最小的语法单元进行解析完成

终结符表达式(Terminal Expression)

抽象表达式的子类,实现与文法中的元素相关联的解释操作,用来定义一个语法中和终止符有关的操作,语法中的每一个终结符都应有一个与之对应的终结表达式。通常,终结符表达式比较简单,主要是处理场景元素和数据的转换

非终结符表达式(Nonterminal Expression)

抽象表达式的子类,用来定义语法中和非终结符有关的操作,非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式
每个非终结符表达式都代表了一个文法规则,并且每个文法规则都只关心自己周边的文法规则的结果(注意是结果),因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式,然后最终、最小的文法规则就是终结符表达式,终结符表达式的概念就是如此,不能够再参与比自己更小的文法运算

环境(Context)

定义各个解释器需要的共享数据或者公共的功能

优点

解释器是一个简单语法分析工具,它最显著的优点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了

缺点

  • 解释器模式会引起类膨胀
  • 解释器模式采用递归调用方法
  • 效率问题

适用场景

  • 重复发生的问题可以使用解释器模式
  • 一个简单语法需要解释的场景

迭代器模式(Iterator Pattern)

Provide a way to access the elements of an aggregate object sequentially without exposing its underlying
representation.(它提供一种方法访问一个容器对象中各个元素,而又不需暴露该对象的内部细节)

角色

Iterator抽象迭代器

抽象迭代器负责定义访问和遍历元素的接口,而且基本上是有固定的3个方法:

  • first()获得第一个元素
  • next()访问下一个元素
  • isDone()是否已经访问到底部(Java叫做hasNext()方法)

ConcreteIterator具体迭代器

具体迭代器角色要实现迭代器接口,完成容器元素的遍历

Aggregate抽象容器

容器角色负责提供创建具体迭代器角色的接口,必然提供一个类似createIterator()这样的方法,在Java中一般是iterator()方法

Concrete Aggregate具体容器

容器实现容器接口定义的方法,创建出容纳迭代器的对象

迭代器模式将遍历集合中所有元素的操作封装成迭代器类,其目的是在不暴露集合对象内部结构的情况下,对外提供统一访问集合的内部数据的方法 迭代器的实现一般包括一个迭代器,用于执行具体的遍历操作;以及一个Collection,用于存储具体的数据

中介者模式(Mediator Pattern)

Define an object that encapsulates how a set of objects interact.Mediator promotes loose coupling by keeping objects
from referring to each other explicitly,and it lets you vary their interaction
independently.(用一个中介对象封装一系列的对象交互,中介者使各对象不需要显示地相互作用,从而使其耦合松散,而且可以独立地改变它们之间的交互)

中介者模式又叫调停模式,是迪米特法则的典型应用,主要特点是将对象与对象之间的关系变为对象和中介者之间的关系,降低了对象之间的耦合性,提高了对象功能的复用性和系统的灵活性,使得系统易于维护和扩展

中介者模式的角色

抽象中介者(Mediator)

抽象中介者角色定义统一的接口,用于各同事角色之间的通信

具体中介者(Concrete Mediator)

具体中介者角色通过协调各同事角色实现协作行为,因此它必须依赖于各个同事角色

同事类(Colleague)

每一个同事角色都知道中介者角色,而且与其他的同事角色通信的时候,一定要通过中介者角色协作。 每个同事类的行为分为两种:
一种是同事本身的行为,比如改变对象本身的状态,处理自己的行为等,这种行为叫做自发行为(Self-Method),与其他的同事类或中介者没有任何的依赖

第二种是必须依赖中介者才能完成的行为,叫做依赖方法(Dep-Method)

优点

中介者模式的优点就是减少类间的依赖,把原有的一对多的依赖变成了一对一的依赖,同事类只依赖中介者,减少了依赖,当然同时也降低了类间的耦合

缺点

中介者模式的缺点就是中介者会膨胀得很大,而且逻辑复杂,原本N个对象直接的相互依赖关系转换为中介者和同事类的依赖关系,同事类越多,中介者的逻辑就越复杂

使用场景

中介者模式适用于多个对象之间紧密耦合的情况,紧密耦合的标准是:在类图中出现了蜘蛛网状结构。在这种情况下一定要考虑使用中介者模式,这有利于把蜘蛛网梳理为星型结构,使原本复杂混乱的关系变得清晰简单

  • N个对象产生了相互依赖的关系(N>2)
  • 多个对象有依赖关系,但是依赖的行为尚不确定或者有发生改变的可能,这种情况下一般使用中介者模式,降低变更引起的风险扩散

备忘录模式(Memento Pattern)

Without violating encapsulation,capture and externalize an object’s internal state so that the object can be restored
to this state later.(在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态)

又叫快照模式,该模式将当前对象的内部状态保存到备忘录中,以便在需要时能将该对象的状态恢复到原先保存的状态
备忘录模式提供了一种保存和恢复状态的机制,常用于快照的记录和状态的存储,使系统发生故障或数据发生不一致时能方便地将数据恢复到某个历史状态 备忘录模式的核心是设计备忘录类及用于管理备忘录的管理者类

角色

Originator 发起人角色

记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据

Memento备忘录角色

负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态

Caretaker备忘录管理员角色

对备忘录进行管理、保存和提供备忘录,定义了保存和获取备忘录状态的功能。注意备忘录只能被保存或恢复,不能被修改

适用场景

  • 需要保存和恢复数据的相关状态的场景
  • 提供一个可回滚(rollbock)的操作
  • 需要监控的副本场景中
  • 数据库连接的事务就是备忘录模式

注意事项

备忘录的生命周期

备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理

备忘录的性能

不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中),原因有二:

一是控制不了备忘录建立的对象数量

二是大对象的建立是要消耗资源的,系统的性能需要考虑

观察者模式(Observer Pattern)

这种模式又叫做发布-订阅模式或者模型-视图模式

Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified
and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新)

指在被观察者的状态发生变化时,系统基于事件驱动理论将其状态通知到订阅其状态的观察者对象中,以完成状态的修改和事件的传播,观察者和被观察者之间的关系属于抽象耦合关系,主要优点是在观察者与被观察者之间建立一套事件触发机制,以降低二者之间的耦合度

角色

Subject被观察者

定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者

Observer观察者

观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理 抽象观察者(Observer)

Concrete Subject 具体的被观察者

定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知

Concrete Observer 具体的观察者

每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑

优点

  • 观察者和被观察者之间是抽象耦合
  • 建立一套触发机制

缺点

需要考虑开发效率和运行效率问题,一个被观察者,多个观察者,开发和调试就会比较复杂,而且在Java中消息的通知默认是顺序执行,一个观察者卡壳,会影响整体的执行效率。在这种情况下,一般考虑采用异步的方式。
多级触发时的效率更是让人担忧,大家在设计时注意考虑

适用场景

  • 关联行为场景,需要注意的是关联行为时可拆分的,而不是组合关系
  • 事件多级触发场景
  • 跨系统的消息交换场景,如消息队列的处理机制

注意事项

广播链问题

它和责任链模式的最大区别就是观察者广播链在传播的过程中消息是随时更改的,它是由相邻的两个节点协商的消息结构;而责任链模式在消息传递过程中基本上保持消息不可变,如果要改变,也只是在原有的消息上进行修正

异步处理问题

状态模式(State Pattern)

Allow an object to alter its behavior when its internal state changes.The object will appear to change its
class.(当一个对象内在状态改变时允许其改变行为,这个对象看起来像改变了其类)

状态模式的核心是封装,状态的变更引起了行为的变更,从外部看起来就好像这个对象对应的类发生了改变一样

指给对象定义不同的状态,并为不同的状态定义不同的行为,在对象的状态发生变换时自动切换状态的行为
该模式将对象的不同行为封装到不同的状态,遵循了“单一职责”原则。同时,状态模式基于对象的状态将对象行为进行了明确的界定,减少了对象行为之间的相互依赖,方便系统的维护和扩展
状态模式把受环境改变的对象的行为包装在不同的对象状态里,用于让一个对象在其内部状态改变时,行为也随之改变

角色

环境(Context)

也叫上下文,用于维护对象当前的状态,并在对象状态发生变化时触发对象行为的变化

抽象状态(Abstract State)

接口或抽象类,负责对象状态定义,并且封装环境角色以实现状态切换

具体状态(Concrete State)

每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理,通俗地说,就是本状态下要做的事情,以及本状态如何过渡到其他状态,具体状态有两个不成文的约束

  • 把状态对象声明为静态常量,有几个状态对象就声明几个静态常量
  • 环境角色具有状态抽象角色定义的所有行为,具体执行使用委托方式

优点

  • 结构清晰

避免了过多的switch…case或者if…else语句的使用,避免了程序的复杂性,提高系统的可维护性

  • 遵循设计原则

很好地体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就要增加子类,你要修改状态,你只修改一个子类就可以了

  • 封装性良好

这也是状态模式的基本要求,状态变换放置到类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换

缺点

子类太多,即类膨胀

使用场景

  • 行为随状态改变而改变的场景
  • 条件、分支语句判断的替代者

    状态模式适用于当某个对象在它的状态发生改变时,它的行为也随着发生比较大的变化,也就是说在行为受状态约束的情况下可以使用状态模式,而且使用时对象的状态最好不要超过5个

策略模式(Strategy Pattern)

也叫政策模式(Policy Pattern)
Define a family of algorithms,encapsulate each one,and make them interchangeable.(定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。)

角色

Context封装角色

它也叫做上下文角色,起承上启下封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化

Strategy抽象策略角色

策略、算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性

ConcreteStrategy具体策略角色

实现抽象策略中的操作,该类含有具体的算法

优点

  • 算法可以自由切换
    这是策略模式本身定义的,只要实现抽象策略,它就成为策略家族的一个成员,通过封装角色对其进行封装,保证对外提供“可自由切换”的策略

  • 避免使用多重条件判断
    由其他模块决定采用何种策略,策略家族对外提供的访问接口就是封装类,简化了操作,同时避免了条件语句判断

  • 扩展性良好

缺点

  • 策略类数量增多
    每一个策略都是一个类,复用的可能性很小,类数量增多

  • 所有的策略类都需要对外暴露
    上层模块必须知道有哪些策略,然后才能决定使用哪一个策略,这与迪米特法则是相违背的

适用场景

  • 多个类只有在算法或者行为上不同的场景
  • 算法需要自由切换的场景
  • 需要屏蔽算法规则的场景

    为同一个行为定义了不同的策略,并为每种策略都实现了不同的方法 在用户使用时,系统根据不同的策略自动切换不同的方法来实现策略的改变 在同一策略下的不同方法是对同一功能的不同实现,因此在使用时可以相互替换而不影响用户的使用

    策略模式的实现是在接口中定义不同的策略,在实现类中完成了对不同策略具体行为的实现,并将用户的策略状态存储在上下文(Context)中来完成策略的存储和状态的改变

模板模式(Template Pattern)

Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclasses
redefine certain steps of an algorithm without changingthe algorithm’s
structure.(定义一个操作中的算法的框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。)

该模式在抽象类中定义了算法的结构并实现了公共部分算法,在子类中实现可变的部分并根据不同的业务需求实现不同的扩展
模板方法的优点在于其在父类(抽象类)中定义了算法的框架以保障算法的稳定性,同时在其父类中实现了算法公共部分的方法来保障代码的复用;将部分算法部分延迟到子类中实现,因此子类可以通过继承的方式来扩展或重新定义算法的功能而不影响算法的稳定性,符合开闭原则
模板模式需要注意抽象类与具体子类之间的协作

优点

  • 封装不变部分,扩展可变部分
    把认为是不变的算法部分封装到父类,而可变的部分则通过继承来实现

  • 提取公共部分代码,便于维护

  • 行为由父类控制,子类实现

缺点

按照设计习惯,抽象类负责声明最抽象、最一般的事物属性和方法,实现类完成具体的事物属性和方法。但是模板方法模式却颠倒了,抽象类定义了部分抽象方法,由子类实现,子类执行的结果影响了父类的结果,也就是子类对父类产生了影响,这在复杂的项目中,会带来代码阅读的难度

使用场景

  • 多个子类有公共的方法,且逻辑基本相同时
  • 重要、复杂的算法,可以把核心算法设计成模板方法,周边的相关细节则由各个子类实现
  • 重构时,模板方法是一个经常使用的模式,把相同的代码抽取到父类中,然后通过钩子函数约束其行为‘

扩展

模板模式的角色

抽象类(Abstract class)

定义了算法的框架,由基本方法和模板方法组成。基本方法定义了算法有哪些环节,模板方法定义了算法在各个环节执行的流程

具体子类(Concrete class)

对在抽象类中定义的算法根据需求进行不同的实现

访问者模式(Visitor Pattern)

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation
without changing the classes of the elements on which it operates.
(封装一些作用于某种数据结构中的各元素的操作,它可以在不改变数据结构的前提下定义作用于这些元素的新的操作)

指将数据结构和对数据的操作分离开来,使其在不改变数据结构的前提下动态添加作用于这些元素上的操作

该模式将数据结构的定义和数据操作的定义分离开来,符合“单一职责”原则。访问者模式定义不同的访问者实现对数据的不同操作,因此需要该数据添加新的操作时只需为其定义一个新的访问者即可

主要特点是将数据结构和作用于结构上的操作解耦,使得集合的操作可以自由地演化而不影响其数据结构。它适用于数据结构稳定但是数据操作方式多变的系统中

访问者模式实现的关键是将作用于元素的操作分离出来封装成独立的类

角色

抽象访问者(Visitor)

定义了一个访问元素的接口,为每类元素都定义了一个访问操作visit(),该操作中的参数类型对应被访问元素的数据类型

具体访问者(Concrete Visitor)

抽象访问者的实现类,实现了不同访问者访问到元素后具体的操作行为

抽象元素(Element)

元素的抽象表示,定义了访问该元素的入口的accept()方法,不同的访问者类型代表不同的访问者 抽象元素有两类方法:

一是本身的业务逻辑,也就是元素作为一个业务处理单元必须完成的职责

另外一个是允许哪一个访问者来访问

具体元素(Concrete Element)

实现抽象元素定义的accept()操作,并根据访问者的不同类型实现不同的业务逻辑

结构对象(ObjectStruture)

元素产生者,一般容纳在多个不同类、不同接口的容器,

优点

  • 符合单一职责原则
  • 优秀的扩展性
  • 灵活性非常高

缺点

  • 具体元素对访问者公布细节
  • 具体元素变更比较困难
  • 违背了依赖倒置原则

适用场景

  • 一个对象结构包含多个对象,它们有不同的接口,而你相对这些对象实施一些依赖于其具体类的操作
  • 需要对一个对象结构中的对象进行很多不同且不相关的操作,而你想避免这些操作“污染”这些对象的类

比较

命令模式VS策略模式

策略模式的意图是封装算法,它认为“算法”已经是一个完整的、不可拆分的原子业务(注意这里是原子业务,而不是原子对象),即其意图是让这些算法独立,并且可以相互替换,让行为的变化独立于拥有行为的客户;而命令模式则是对动作的解耦,把一个动作的执行分为执行对象(接收者角色)、执行行为(命令角色),让两者相互独立而不相互影响

  • 关注点不同

    • 策略模式关注的是算法替换的问题,一个新的算法投产,旧算法退休,或者提供多种算法由调用者选择,算法的自由更替是它实现的要点。换句话说,策略模式关注的是算法的完整性、封装性,只有具备了这两个条件才能保证其可以自由切换。

    • 命令模式则关注的是解耦问题,如何让请求者和执行者解耦是它首先解决的问题,解耦的要求就是把请求的内容封装为一个一个的命令,由接收者执行。由于封装成了命令,就同时可以对命令进行多种处理,例如撤销、记录等

  • 角色功能不同
    策略模式中的具体算法是负责一个完整算法逻辑,它是不可再拆分的原子业务单元,一旦变更就是对算法整体的变更 命令模式则不同,它关注命令的实现,也就是功能的实现

  • 使用场景不同
    策略模式适用于算法要求变化的场景,而命令模式适用于解耦两个有紧耦合关系的对象场合或多命令撤销场合

策略模式VS状态模式

策略模式封装的是不同的算法,算法之间没有交互,以达到算法可以自由切换的目的;而状态模式封装的是不同的状态,以达到状态切换行为随之发生改变的目的。这两种模式虽然都有变换的行为,但是两者的目标却是不同的