行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。

行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式是 GoF 设计模式中最为庞大的一类,它包含以下 11 种模式。

  1. 模板方法(Template Method)模式:定义一个操作中的算法骨架,将算法的一些步骤延迟到子类中,使得子类在可以不改变该算法结构的情况下重定义该算法的某些特定步骤。
  2. 策略(Strategy)模式:定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的改变不会影响使用算法的客户。
  3. 命令(Command)模式:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。
  4. 职责链(Chain of Responsibility)模式:把请求从链中的一个对象传到下一个对象,直到请求被响应为止。通过这种方式去除对象之间的耦合。
  5. 状态(State)模式:允许一个对象在其内部状态发生改变时改变其行为能力。
  6. 观察者(Observer)模式:多个对象间存在一对多关系,当一个对象发生改变时,把这种改变通知给其他多个对象,从而影响其他对象的行为。
  7. 中介者(Mediator)模式:定义一个中介对象来简化原有对象之间的交互关系,降低系统中对象间的耦合度,使原有对象之间不必相互了解。
  8. 迭代器(Iterator)模式:提供一种方法来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
  9. 访问者(Visitor)模式:在不改变集合元素的前提下,为一个集合中的每个元素提供多种访问方式,即每个元素有多个访问者对象访问。
  10. 备忘录(Memento)模式:在不破坏封装性的前提下,获取并保存一个对象的内部状态,以便以后恢复它。
  11. 解释器(Interpreter)模式:提供如何定义语言的文法,以及对语言句子的解释方法,即解释器。

    1.模板方法模式

    程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。

在生活中也一样, 我们每天起床 刷牙 吃饭, 但是吃饭每个人吃的东西可能都不一样, 但是顺序是定的 .模板方法也就是为了解决上述问题的

模式的定义与特点

模板方法(Template Method)模式的定义如下: 定义一个操作中的算法骨架(执行顺序之类的),将不变的放在骨架中, 变化的放到子类中去实现

说白了就是 客户访问接口, 接口的实现类里会设计好执行顺序,有一些是这个实现类固有的方法, 有一些是需要子类去重写的, 然后运行这些方法, 子类的方法还可以改变父类的方法(被改变的父类方法就叫钩子方法)

优点:
1.它封装了不变部分,扩展可变部分。
2.它在父类中提取了公共的部分代码,便于代码复用
3.部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

缺点:
1.每一个子类都会增加系统的复杂性,而他的功能扩展就是靠子类
2.父类的方法由子类实现,这是一种反向控制的结构, 反向控制的可读性差
3.由于继承的关系, 导致如果修改父类的抽象方法, 那么每个子类也都得改

模式的结构与实现

1)抽象类/抽象模板(Abstract Class)

抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下。

① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。

② 基本方法:是整个算法中的一个步骤,包含以下几种类型。

  • 抽象方法:在抽象类中声明,由具体子类实现。
  • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它。
  • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。

    2)具体子类/具体实现(Concrete Class)

    具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

行为型模式的特点和分类 - 图1

  1. public class TemplateMethodPattern {
  2. public static void main(String[] args) {
  3. AbstractClass tm = new ConcreteClass();
  4. tm.TemplateMethod();
  5. }
  6. }
  7. //抽象类
  8. abstract class AbstractClass {
  9. //模板方法
  10. public void TemplateMethod() {
  11. SpecificMethod();
  12. abstractMethod1();
  13. abstractMethod2();
  14. }
  15. //具体方法
  16. public void SpecificMethod() {
  17. System.out.println("抽象类中的具体方法被调用...");
  18. }
  19. //抽象方法1
  20. public abstract void abstractMethod1();
  21. //抽象方法2
  22. public abstract void abstractMethod2();
  23. }
  24. //具体子类
  25. class ConcreteClass extends AbstractClass {
  26. public void abstractMethod1() {
  27. System.out.println("抽象方法1的实现被调用...");
  28. }
  29. public void abstractMethod2() {
  30. System.out.println("抽象方法2的实现被调用...");
  31. }
  32. }
  33. 执行结果为
  34. 抽象类中的具体方法被调用...
  35. 抽象方法1的实现被调用...
  36. 抽象方法2的实现被调用...

模式的扩展

在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果,其结构图如图 3 所示。

行为型模式的特点和分类 - 图2

  1. public class HookTemplateMethod {
  2. public static void main(String[] args) {
  3. HookAbstractClass tm = new HookConcreteClass();
  4. tm.TemplateMethod();
  5. }
  6. }
  7. //含钩子方法的抽象类
  8. abstract class HookAbstractClass {
  9. //模板方法
  10. public void TemplateMethod() {
  11. abstractMethod1();
  12. HookMethod1();
  13. if (HookMethod2()) {
  14. SpecificMethod();
  15. }
  16. abstractMethod2();
  17. }
  18. //具体方法
  19. public void SpecificMethod() {
  20. System.out.println("抽象类中的具体方法被调用...");
  21. }
  22. //钩子方法1
  23. public void HookMethod1() {
  24. }
  25. //钩子方法2
  26. public boolean HookMethod2() {
  27. return true;
  28. }
  29. //抽象方法1
  30. public abstract void abstractMethod1();
  31. //抽象方法2
  32. public abstract void abstractMethod2();
  33. }
  34. //含钩子方法的具体子类
  35. class HookConcreteClass extends HookAbstractClass {
  36. public void abstractMethod1() {
  37. System.out.println("抽象方法1的实现被调用...");
  38. }
  39. public void abstractMethod2() {
  40. System.out.println("抽象方法2的实现被调用...");
  41. }
  42. public void HookMethod1() {
  43. System.out.println("钩子方法1被重写...");
  44. }
  45. public boolean HookMethod2() {
  46. return false;
  47. }
  48. }
  49. 抽象方法1的实现被调用...
  50. 钩子方法1被重写...
  51. 抽象方法2的实现被调用...

上面这种就是通过子类去重写钩子方法,进而来控制其父类

2.策略模式

在生活中 假如我们放假回老家, 会有很多种策略, 坐飞机,坐火车 ,坐大巴等 每一种交通方式都是不同的策略
带代码中,针对客户的不同需求会有不同的策略,但是并不改变客户对客户端感知,这便是策略模式的用意

策略模式的定义与特点

策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的重点不是创造对象,而是组织对象

说白了就是: 一个接口有不同的实现类,通过环境类(也可以通过xml,hashMap之类的来控制对不同的实现类的访问)来调用不同实现类的同名方法达到实现不同策略的目的. 利用的其实就是多态性,接口的实现类不同 方法就会不同
中心思想就是:一个接口针对不同的情况给出不同的方法, 用实现类来实现

策略模式的主要优点如下。

  1. 多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句。
  2. 策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
  3. 策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
  4. 策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
  5. 策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。

其主要缺点如下。

  1. 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类。
  2. 策略模式造成很多的策略类,增加维护难度。

策略模式的结构与实现

1. 模式的结构

策略模式的主要角色如下。

  1. 抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现。
  2. 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现。
  3. 环境(Context)类:持有一个策略类的引用,最终给客户端调用。

行为型模式的特点和分类 - 图3

2. 模式的实现

策略模式的实现代码如下:

  1. public class StrategyPattern {
  2. public static void main(String[] args) {
  3. Context c = new Context();
  4. Strategy s = new ConcreteStrategyA();
  5. c.setStrategy(s);
  6. c.strategyMethod();
  7. System.out.println("-----------------");
  8. s = new ConcreteStrategyB();
  9. c.setStrategy(s);
  10. c.strategyMethod();
  11. }
  12. }
  13. //抽象策略类
  14. interface Strategy {
  15. public void strategyMethod(); //策略方法
  16. }
  17. //具体策略类A
  18. class ConcreteStrategyA implements Strategy {
  19. public void strategyMethod() {
  20. System.out.println("具体策略A的策略方法被访问!");
  21. }
  22. }
  23. //具体策略类B
  24. class ConcreteStrategyB implements Strategy {
  25. public void strategyMethod() {
  26. System.out.println("具体策略B的策略方法被访问!");
  27. }
  28. }
  29. //环境类
  30. class Context {
  31. private Strategy strategy;
  32. public Strategy getStrategy() {
  33. return strategy;
  34. }
  35. public void setStrategy(Strategy strategy) {
  36. this.strategy = strategy;
  37. }
  38. public void strategyMethod() {
  39. strategy.strategyMethod();
  40. }
  41. }

策略模式的扩展

在一个使用策略模式的系统中,当存在的策略很多时,客户端管理所有策略算法将变得很复杂,如果在环境类中使用策略工厂模式来管理这些策略类将大大减少客户端的工作复杂度,其结构图如图 5 所示。

行为型模式的特点和分类 - 图4

3.命令模式

在软件开发系统中,“方法的请求者”与“方法的实现者”之间经常存在紧密的耦合关系,这不利于软件功能的扩展与维护,“如何将方法的请求者与实现者解耦?”变得很重要,命令模式就能很好地解决这个问题。

在生活中 我们用遥控器 控制电视, 这就是请求与实现者的分离, 我们是请求者, 电视机是接收者,我们改变自己并不影响电视机使用,我们通过中间件(遥控器)来控制电视机, 在程序中我们能做到这样就可达到我们命令模式的要求了
也就是说白了, 你改变调用者的代码不会影响到接收者 这便达到了隔离的目的,利用聚合关系来解耦

命令模式存在的意义就是有时候请求会很多变的

命令模式的定义与特点

命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理。
命令模式的主要优点如下。

  1. 通过引入中间件(抽象接口)降低系统的耦合度。
  2. 扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”。
  3. 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
  4. 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
  5. 可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活。

其缺点是:

  1. 可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性。
  2. 命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解。

    命令模式的结构与实现

    可以将系统中的相关操作抽象成命令,使调用者与实现者相关分离,其结构如下。

1. 模式的结构

命令模式包含以下主要角色。

  1. 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()。例如遥控器
  2. 具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作。例如开机按钮
  3. 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者。 电视机
  4. 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者。 我们

行为型模式的特点和分类 - 图5

2. 模式的实现

命令模式的代码如下:

  1. package command;
  2. public class CommandPattern {
  3. public static void main(String[] args) {
  4. Command cmd = new ConcreteCommand();
  5. Invoker ir = new Invoker(cmd);
  6. System.out.println("客户访问调用者的call()方法...");
  7. ir.call();
  8. }
  9. }
  10. //调用者
  11. class Invoker {
  12. private Command command;
  13. public Invoker(Command command) {
  14. this.command = command;
  15. }
  16. public void setCommand(Command command) {
  17. this.command = command;
  18. }
  19. public void call() {
  20. System.out.println("调用者执行命令command...");
  21. command.execute();
  22. }
  23. }
  24. //抽象命令
  25. interface Command {
  26. public abstract void execute();
  27. }
  28. //具体命令
  29. class ConcreteCommand implements Command {
  30. private Receiver receiver;
  31. ConcreteCommand() {
  32. receiver = new Receiver();
  33. }
  34. public void execute() {
  35. receiver.action();
  36. }
  37. }
  38. //接收者
  39. class Receiver {
  40. public void action() {
  41. System.out.println("接收者的action()方法被调用...");
  42. }
  43. }
  44. 执行结果
  45. 客户访问调用者的call()方法...
  46. 调用者执行命令command...
  47. 接收者的action()方法被调用...

从代码中可以看到 你不管怎么改变你的调用者代码 ,都不会影响接收者, 这就是隔离了

命令模式的应用场景

当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。

命令模式通常适用于以下场景。

  1. 请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互。
  2. 系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能。
  3. 当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能。
  4. 当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现。

    命令模式的扩展

    在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令,其具体结构图如图 3 所示。

行为型模式的特点和分类 - 图6

  1. package command;
  2. import java.util.ArrayList;
  3. public class CompositeCommandPattern {
  4. public static void main(String[] args) {
  5. AbstractCommand cmd1 = new ConcreteCommand1();
  6. AbstractCommand cmd2 = new ConcreteCommand2();
  7. CompositeInvoker ir = new CompositeInvoker();
  8. ir.add(cmd1);
  9. ir.add(cmd2);
  10. System.out.println("客户访问调用者的execute()方法...");
  11. ir.execute();
  12. }
  13. }
  14. //抽象命令
  15. interface AbstractCommand {
  16. public abstract void execute();
  17. }
  18. //树叶构件: 具体命令1
  19. class ConcreteCommand1 implements AbstractCommand {
  20. private CompositeReceiver receiver;
  21. ConcreteCommand1() {
  22. receiver = new CompositeReceiver();
  23. }
  24. public void execute() {
  25. receiver.action1();
  26. }
  27. }
  28. //树叶构件: 具体命令2
  29. class ConcreteCommand2 implements AbstractCommand {
  30. private CompositeReceiver receiver;
  31. ConcreteCommand2() {
  32. receiver = new CompositeReceiver();
  33. }
  34. public void execute() {
  35. receiver.action2();
  36. }
  37. }
  38. //树枝构件: 调用者
  39. class CompositeInvoker implements AbstractCommand {
  40. private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>();
  41. public void add(AbstractCommand c) {
  42. children.add(c);
  43. }
  44. public void remove(AbstractCommand c) {
  45. children.remove(c);
  46. }
  47. public AbstractCommand getChild(int i) {
  48. return children.get(i);
  49. }
  50. public void execute() {
  51. for (Object obj : children) {
  52. ((AbstractCommand) obj).execute();
  53. }
  54. }
  55. }
  56. //接收者
  57. class CompositeReceiver {
  58. public void action1() {
  59. System.out.println("接收者的action1()方法被调用...");
  60. }
  61. public void action2() {
  62. System.out.println("接收者的action2()方法被调用...");
  63. }
  64. }

4.责任链模式(职责链模式)

在现实的生活中我们要做一件事 会有很多步骤, 比如 我们要去看胃病, 要先挂号,再去取挂号单,再去对应的诊室门诊,最后还要去取药, 这一环接着一环 都需要我们知道下一步要干嘛
代码中, 一个请求在传入处理器后要经经历一系列的操作最终成为预期结果, 如果请求每次访问一次处理器都要去手动设置下一个要访问哪个处理器,这样很费劲, 因此就有了责任链模式, 请求跟实现分离, 你无需关心它到底要怎么走, 它会根据自身的条件来判断要去哪个处理器 这便是责任链

模式的定义与特点

责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。

在责任链模式中,客户只需要将请求发送到设置好的责任链上即可(这个责任链需要单独设置),无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。

说白了,就是你只要传进来请求, 程序自动帮你调用不同的handler帮你处理了, 至于怎么处理,顺序怎样 你不必关心,能做到这样,让其按照链式自行运行,便是实现了责任链模式了, 拓展就通过继承(重写抽象方法)或者实现接口来拓展, 请求会带参数, 每个处理器都能判断参数来判断是否满足本handler的执行要求

责任链跟策略的区别: 策略模式是平铺的感觉,环境控制类根据条件来调用不同的handler来处理, 而责任链模式更类似于是纵向的,它主要的作用是减少if else /switch catch, 它每个handler会都会指向下一个handler,直到找到能处理该请求的handler.,而策略不一样,策略的handler不会指向下一个, 而是直接进行处理

责任链模式是一种对象行为型模式,其主要优点如下。

  1. 降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息。
  2. 增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则。
  3. 增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任。
  4. 责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
  5. 责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。

其主要缺点如下。

  1. 不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响。
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

模式的结构与实现

1. 模式的结构

职责链模式主要包含以下角色。

  1. 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
  2. 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
  3. 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。

行为型模式的特点和分类 - 图7

行为型模式的特点和分类 - 图8

2. 模式的实现

职责链模式的实现代码如下:

  1. package chainOfResponsibility;
  2. public class ChainOfResponsibilityPattern {
  3. public static void main(String[] args) {
  4. //组装责任链
  5. Handler handler1 = new ConcreteHandler1();
  6. Handler handler2 = new ConcreteHandler2();
  7. handler1.setNext(handler2);
  8. //提交请求
  9. handler1.handleRequest("two");
  10. }
  11. }
  12. //抽象处理者角色
  13. abstract class Handler {
  14. private Handler next;
  15. public void setNext(Handler next) {
  16. this.next = next;
  17. }
  18. public Handler getNext() {
  19. return next;
  20. }
  21. //处理请求的方法
  22. public abstract void handleRequest(String request);
  23. }
  24. //具体处理者角色1
  25. class ConcreteHandler1 extends Handler {
  26. public void handleRequest(String request) {
  27. if (request.equals("one")) {
  28. System.out.println("具体处理者1负责处理该请求!");
  29. } else {
  30. if (getNext() != null) {
  31. getNext().handleRequest(request);
  32. } else {
  33. System.out.println("没有人处理该请求!");
  34. }
  35. }
  36. }
  37. }
  38. //具体处理者角色2
  39. class ConcreteHandler2 extends Handler {
  40. public void handleRequest(String request) {
  41. if (request.equals("two")) {
  42. System.out.println("具体处理者2负责处理该请求!");
  43. } else {
  44. if (getNext() != null) {
  45. getNext().handleRequest(request);
  46. } else {
  47. System.out.println("没有人处理该请求!");
  48. }
  49. }
  50. }
  51. }
  52. 程序运行结果如下:
  53. 具体处理者2负责处理该请求!

在上面代码中,我们把消息硬编码为 String 类型,而在真实业务中,消息是具备多样性的,可以是 int、String 或者自定义类型。因此,在上面代码的基础上,可以对消息类型进行抽象 Request,增强了消息的兼容性。

模式的扩展

职责链模式存在以下两种情况。

  1. 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
  2. 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。

    5.状态模式

    在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。
    在我们的代码中有时候会根据if else 或 switch case 来判断状态 来决定不同的行为, 这样的扩展性不强, 而状态模式 就是让对象具有状态的同时具有行为, 你传进来的对象对应着不同状态下的行为,就解决了这样的问题

本文中的方法, 就是通过环境类聚合状态接口, 然后传不不同的接口实现类,实现类自带状态跟行为

状态模式的定义与特点

状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。

状态模式是一种对象行为型模式,其主要优点如下。

  1. 结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
  2. 将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
  3. 状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。

状态模式的主要缺点如下。

  1. 状态模式的使用必然会增加系统的类与对象的个数。
  2. 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
  3. 状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。

状态模式的结构与实现

1. 模式的结构

状态模式包含以下主要角色。

  1. 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  2. 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  3. 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

行为型模式的特点和分类 - 图9

2. 模式的实现

状态模式的实现代码如下:

  1. public class StatePatternClient {
  2. public static void main(String[] args) {
  3. Context context = new Context(); //创建环境
  4. context.Handle(); //处理请求
  5. context.Handle();
  6. context.Handle();
  7. context.Handle();
  8. }
  9. }
  10. //环境类
  11. class Context {
  12. private State state;
  13. //定义环境类的初始状态
  14. public Context() {
  15. this.state = new ConcreteStateA();
  16. }
  17. //设置新状态
  18. public void setState(State state) {
  19. this.state = state;
  20. }
  21. //读取状态
  22. public State getState() {
  23. return (state);
  24. }
  25. //对请求做处理
  26. public void Handle() {
  27. state.Handle(this);
  28. }
  29. }
  30. //抽象状态类
  31. abstract class State {
  32. public abstract void Handle(Context context);
  33. }
  34. //具体状态A类
  35. class ConcreteStateA extends State {
  36. public void Handle(Context context) {
  37. System.out.println("当前状态是 A.");
  38. context.setState(new ConcreteStateB());
  39. }
  40. }
  41. //具体状态B类
  42. class ConcreteStateB extends State {
  43. public void Handle(Context context) {
  44. System.out.println("当前状态是 B.");
  45. context.setState(new ConcreteStateA());
  46. }
  47. }
  48. 运行结果
  49. 当前状态是 A.
  50. 当前状态是 B.
  51. 当前状态是 A.
  52. 当前状态是 B.

【例2】用“状态模式”设计一个多线程的状态转换程序。
分析:多线程存在 5 种状态,分别为新建状态、就绪状态、运行状态、阻塞状态和死亡状态,各个状态当遇到相关方法调用或事件触发时会转换到其他状态,其状态转换规律如图 3 所示。

行为型模式的特点和分类 - 图10
现在先定义一个抽象状态类(TheadState),然后为图 3 所示的每个状态设计一个具体状态类,它们是新建状态(New)、就绪状态(Runnable )、运行状态(Running)、阻塞状态(Blocked)和死亡状态(Dead),每个状态中有触发它们转变状态的方法,环境类(ThreadContext)中先生成一个初始状态(New),并提供相关触发方法,图 4 所示是线程状态转换程序的结构图。

行为型模式的特点和分类 - 图11

  1. public class ScoreStateTest {
  2. public static void main(String[] args) {
  3. ThreadContext context = new ThreadContext();
  4. context.start();
  5. context.getCPU();
  6. context.suspend();
  7. context.resume();
  8. context.getCPU();
  9. context.stop();
  10. }
  11. }
  12. //环境类
  13. class ThreadContext {
  14. private ThreadState state;
  15. ThreadContext() {
  16. state = new New();
  17. }
  18. public void setState(ThreadState state) {
  19. this.state = state;
  20. }
  21. public ThreadState getState() {
  22. return state;
  23. }
  24. public void start() {
  25. ((New) state).start(this);
  26. }
  27. public void getCPU() {
  28. ((Runnable) state).getCPU(this);
  29. }
  30. public void suspend() {
  31. ((Running) state).suspend(this);
  32. }
  33. public void stop() {
  34. ((Running) state).stop(this);
  35. }
  36. public void resume() {
  37. ((Blocked) state).resume(this);
  38. }
  39. }
  40. //抽象状态类:线程状态
  41. abstract class ThreadState {
  42. protected String stateName; //状态名
  43. }
  44. //具体状态类:新建状态
  45. class New extends ThreadState {
  46. public New() {
  47. stateName = "新建状态";
  48. System.out.println("当前线程处于:新建状态.");
  49. }
  50. public void start(ThreadContext hj) {
  51. System.out.print("调用start()方法-->");
  52. if (stateName.equals("新建状态")) {
  53. hj.setState(new Runnable());
  54. } else {
  55. System.out.println("当前线程不是新建状态,不能调用start()方法.");
  56. }
  57. }
  58. }
  59. //具体状态类:就绪状态
  60. class Runnable extends ThreadState {
  61. public Runnable() {
  62. stateName = "就绪状态";
  63. System.out.println("当前线程处于:就绪状态.");
  64. }
  65. public void getCPU(ThreadContext hj) {
  66. System.out.print("获得CPU时间-->");
  67. if (stateName.equals("就绪状态")) {
  68. hj.setState(new Running());
  69. } else {
  70. System.out.println("当前线程不是就绪状态,不能获取CPU.");
  71. }
  72. }
  73. }
  74. //具体状态类:运行状态
  75. class Running extends ThreadState {
  76. public Running() {
  77. stateName = "运行状态";
  78. System.out.println("当前线程处于:运行状态.");
  79. }
  80. public void suspend(ThreadContext hj) {
  81. System.out.print("调用suspend()方法-->");
  82. if (stateName.equals("运行状态")) {
  83. hj.setState(new Blocked());
  84. } else {
  85. System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
  86. }
  87. }
  88. public void stop(ThreadContext hj) {
  89. System.out.print("调用stop()方法-->");
  90. if (stateName.equals("运行状态")) {
  91. hj.setState(new Dead());
  92. } else {
  93. System.out.println("当前线程不是运行状态,不能调用stop()方法.");
  94. }
  95. }
  96. }
  97. //具体状态类:阻塞状态
  98. class Blocked extends ThreadState {
  99. public Blocked() {
  100. stateName = "阻塞状态";
  101. System.out.println("当前线程处于:阻塞状态.");
  102. }
  103. public void resume(ThreadContext hj) {
  104. System.out.print("调用resume()方法-->");
  105. if (stateName.equals("阻塞状态")) {
  106. hj.setState(new Runnable());
  107. } else {
  108. System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
  109. }
  110. }
  111. }
  112. //具体状态类:死亡状态
  113. class Dead extends ThreadState {
  114. public Dead() {
  115. stateName = "死亡状态";
  116. System.out.println("当前线程处于:死亡状态.");
  117. }
  118. }
  119. 运行结果如下
  120. 当前线程处于:新建状态.
  121. 调用start()方法-->当前线程处于:就绪状态.
  122. 获得CPU时间-->当前线程处于:运行状态.
  123. 调用suspend()方法-->当前线程处于:阻塞状态.
  124. 调用resume()方法-->当前线程处于:就绪状态.
  125. 获得CPU时间-->当前线程处于:运行状态.
  126. 调用stop()方法-->当前线程处于:死亡状态.

状态模式的应用场景

通常在以下情况下可以考虑使用状态模式。

  • 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式。
  • 一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时。

    状态模式的扩展

    在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,其结构图如图 5 所示。行为型模式的特点和分类 - 图12
    分析:共享状态模式的不同之处是在环境类中增加了一个 HashMap 来保存相关状态(这样就不用重新创建新对象了),当需要某种状态时可以从中获取,其程序代码如下
    1. package state;
    2. import java.util.HashMap;
    3. public class FlyweightStatePattern {
    4. public static void main(String[] args) {
    5. ShareContext context = new ShareContext(); //创建环境
    6. context.Handle(); //处理请求
    7. context.Handle();
    8. context.Handle();
    9. context.Handle();
    10. }
    11. }
    12. //环境类
    13. class ShareContext {
    14. private ShareState state;
    15. private HashMap<String, ShareState> stateSet = new HashMap<String, ShareState>();
    16. public ShareContext() {
    17. state = new ConcreteState1();
    18. stateSet.put("1", state);
    19. state = new ConcreteState2();
    20. stateSet.put("2", state);
    21. state = getState("1");
    22. }
    23. //设置新状态
    24. public void setState(ShareState state) {
    25. this.state = state;
    26. }
    27. //读取状态
    28. public ShareState getState(String key) {
    29. ShareState s = (ShareState) stateSet.get(key);
    30. return s;
    31. }
    32. //对请求做处理
    33. public void Handle() {
    34. state.Handle(this);
    35. }
    36. }
    37. //抽象状态类
    38. abstract class ShareState {
    39. public abstract void Handle(ShareContext context);
    40. }
    41. //具体状态1类
    42. class ConcreteState1 extends ShareState {
    43. public void Handle(ShareContext context) {
    44. System.out.println("当前状态是: 状态1");
    45. context.setState(context.getState("2"));
    46. }
    47. }
    48. //具体状态2类
    49. class ConcreteState2 extends ShareState {
    50. public void Handle(ShareContext context) {
    51. System.out.println("当前状态是: 状态2");
    52. context.setState(context.getState("1"));
    53. }
    54. }
    55. 结果
    56. 当前状态是: 状态1
    57. 当前状态是: 状态2
    58. 当前状态是: 状态1
    59. 当前状态是: 状态2

    拓展

    状态模式与责任链模式的区别

    状态模式和责任链模式都能消除 if-else 分支过多的问题。但在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。

从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。

从代码实现上来看,两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。

状态模式与策略模式的区别

状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。

6.观察者模式

观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。

思想便是 目标的具体实现类发出通知, 订阅类接受通知执行代码(看起来 就像他观察到了变化而变化),将原来耦合的两个关系 分割开来, 分割成独立的部分,然后通过消息来控制另一方跟着改变方而改变.这样能松耦合

目标类发生变化,观察类观察到目标类变化而变化

观察者模式是一种对象行为型模式,其主要优点如下。

  1. 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
  2. 目标与观察者之间建立了一套触发机制。

它的主要缺点如下。

  1. 目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
  2. 当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。

模式的结构与实现

实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。

1. 模式的结构

观察者模式的主要角色如下。

  1. 抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法。
  2. 具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象。
  3. 抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用。
  4. 具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态。

观察者模式的结构图如图 1 所示

行为型模式的特点和分类 - 图13

2. 模式的实现

观察者模式的实现代码如下:

  1. package net.biancheng.c.observer;
  2. import java.util.*;
  3. public class ObserverPattern {
  4. public static void main(String[] args) {
  5. Subject subject = new ConcreteSubject();
  6. Observer obs1 = new ConcreteObserver1();
  7. Observer obs2 = new ConcreteObserver2();
  8. subject.add(obs1);
  9. subject.add(obs2);
  10. subject.notifyObserver();
  11. }
  12. }
  13. //抽象目标
  14. abstract class Subject {
  15. protected List<Observer> observers = new ArrayList<Observer>();
  16. //增加观察者方法
  17. public void add(Observer observer) {
  18. observers.add(observer);
  19. }
  20. //删除观察者方法
  21. public void remove(Observer observer) {
  22. observers.remove(observer);
  23. }
  24. public abstract void notifyObserver(); //通知观察者方法
  25. }
  26. //具体目标
  27. class ConcreteSubject extends Subject {
  28. public void notifyObserver() {
  29. System.out.println("具体目标发生改变...");
  30. System.out.println("--------------");
  31. for (Object obs : observers) {
  32. ((Observer) obs).response();
  33. }
  34. }
  35. }
  36. //抽象观察者
  37. interface Observer {
  38. void response(); //反应
  39. }
  40. //具体观察者1
  41. class ConcreteObserver1 implements Observer {
  42. public void response() {
  43. System.out.println("具体观察者1作出反应!");
  44. }
  45. }
  46. //具体观察者1
  47. class ConcreteObserver2 implements Observer {
  48. public void response() {
  49. System.out.println("具体观察者2作出反应!");
  50. }
  51. }
  52. 具体目标发生改变...
  53. --------------
  54. 具体观察者1作出反应!
  55. 具体观察者2作出反应!

模式的应用场景

在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。

通过前面的分析与应用实例可知观察者模式适合以下几种情形。

  1. 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
  3. 实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播。
  4. 多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知。

    模式的扩展

    Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。

1. Observable类

Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。

  1. void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
  2. void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
  3. void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。

    2. Observer 接口

    Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。

【例3】利用 Observable 类和 Observer 接口实现原油期货的观察者模式实例。
分析:当原油价格上涨时,空方伤心,多方局兴;当油价下跌时,空方局兴,多方伤心。本实例中的抽象目标(Observable)类在 Java 中已经定义,可以直接定义其子类,即原油期货(OilFutures)类,它是具体目标类,该类中定义一个 SetPriCe(float price) 方法,当原油数据发生变化时调用其父类的 notifyObservers(Object arg) 方法来通知所有观察者;另外,本实例中的抽象观察者接口(Observer)在 Java 中已经定义,只要定义其子类,即具体观察者类(包括多方类 Bull 和空方类 Bear),并实现 update(Observable o,Object arg) 方法即可。图 5 所示是其结构图。

行为型模式的特点和分类 - 图14

  1. package net.biancheng.c.observer;
  2. import java.util.Observer;
  3. import java.util.Observable;
  4. public class CrudeOilFutures {
  5. public static void main(String[] args) {
  6. OilFutures oil = new OilFutures();
  7. Observer bull = new Bull(); //多方
  8. Observer bear = new Bear(); //空方
  9. oil.addObserver(bull);
  10. oil.addObserver(bear);
  11. oil.setPrice(10);
  12. oil.setPrice(-8);
  13. }
  14. }
  15. //具体目标类:原油期货
  16. class OilFutures extends Observable {
  17. private float price;
  18. public float getPrice() {
  19. return this.price;
  20. }
  21. public void setPrice(float price) {
  22. super.setChanged(); //设置内部标志位,注明数据发生变化
  23. super.notifyObservers(price); //通知观察者价格改变了
  24. this.price = price;
  25. }
  26. }
  27. //具体观察者类:多方
  28. class Bull implements Observer {
  29. public void update(Observable o, Object arg) {
  30. Float price = ((Float) arg).floatValue();
  31. if (price > 0) {
  32. System.out.println("油价上涨" + price + "元,多方高兴了!");
  33. } else {
  34. System.out.println("油价下跌" + (-price) + "元,多方伤心了!");
  35. }
  36. }
  37. }
  38. //具体观察者类:空方
  39. class Bear implements Observer {
  40. public void update(Observable o, Object arg) {
  41. Float price = ((Float) arg).floatValue();
  42. if (price > 0) {
  43. System.out.println("油价上涨" + price + "元,空方伤心了!");
  44. } else {
  45. System.out.println("油价下跌" + (-price) + "元,空方高兴了!");
  46. }
  47. }
  48. }
  49. 油价上涨10.0元,空方伤心了!
  50. 油价上涨10.0元,多方高兴了!
  51. 油价下跌8.0元,空方高兴了!
  52. 油价下跌8.0元,多方伤心了!

7.中介者模式

在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。

如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。

在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。

模式的定义与特点

中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。

说白了,就是通过中介进行注册和转发,双向改变,将对象间的一对多关联转变为一对一的关联
它强调的是同事类之间的交互
中介者模式是一种对象行为型模式,其主要优点如下。

  1. 类之间各司其职,符合迪米特法则。
  2. 降低了对象之间的耦合性,使得对象易于独立地被复用。
  3. 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展。

其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。

模式的结构与实现

1. 模式的结构

中介者模式包含以下主要角色。

  1. 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
  2. 具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
  3. 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
  4. 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。行为型模式的特点和分类 - 图15

    2. 模式的实现

    中介者模式的实现代码如下: ```java package net.biancheng.c.mediator; import java.util.*; public class MediatorPattern { public static void main(String[] args) {
    1. Mediator md = new ConcreteMediator();
    2. Colleague c1, c2;
    3. c1 = new ConcreteColleague1();
    4. c2 = new ConcreteColleague2();
    5. md.register(c1);
    6. md.register(c2);
    7. c1.send();
    8. System.out.println("-------------");
    9. c2.send();
    } } //抽象中介者 abstract class Mediator { public abstract void register(Colleague colleague); public abstract void relay(Colleague cl); //转发 } //具体中介者 class ConcreteMediator extends Mediator { private List colleagues = new ArrayList(); public void register(Colleague colleague) {
    1. if (!colleagues.contains(colleague)) {
    2. colleagues.add(colleague);
    3. colleague.setMedium(this);
    4. }
    } public void relay(Colleague cl) {
    1. for (Colleague ob : colleagues) {
    2. if (!ob.equals(cl)) {
    3. ((Colleague) ob).receive();
    4. }
    5. }
    } } //抽象同事类 abstract class Colleague { protected Mediator mediator; public void setMedium(Mediator mediator) {
    1. this.mediator = mediator;
    } public abstract void receive(); public abstract void send(); } //具体同事类 class ConcreteColleague1 extends Colleague { public void receive() {
    1. System.out.println("具体同事类1收到请求。");
    } public void send() {
    1. System.out.println("具体同事类1发出请求。");
    2. mediator.relay(this); //请中介者转发
    } } //具体同事类 class ConcreteColleague2 extends Colleague { public void receive() {
    1. System.out.println("具体同事类2收到请求。");
    } public void send() {
    1. System.out.println("具体同事类2发出请求。");
    2. mediator.relay(this); //请中介者转发
    } }

具体同事类1发出请求。

具体同事类2收到请求。

具体同事类2发出请求。 具体同事类1收到请求。

  1. <a name="QqEIB"></a>
  2. ## 模式的扩展
  3. 在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单。
  4. 1. 不定义中介者接口,把具体中介者对象实现成为单例。
  5. 1. 同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用。
  6. ![](https://cdn.nlark.com/yuque/0/2021/gif/2126816/1614586690676-2b9093ea-f945-4625-8598-291b3a249128.gif#align=left&display=inline&height=408&margin=%5Bobject%20Object%5D&originHeight=408&originWidth=750&size=0&status=done&style=none&width=750)
  7. ```java
  8. package net.biancheng.c.mediator;
  9. import java.util.*;
  10. public class SimpleMediatorPattern {
  11. public static void main(String[] args) {
  12. SimpleColleague c1, c2;
  13. c1 = new SimpleConcreteColleague1();
  14. c2 = new SimpleConcreteColleague2();
  15. c1.send();
  16. System.out.println("-----------------");
  17. c2.send();
  18. }
  19. }
  20. //简单单例中介者
  21. class SimpleMediator {
  22. private static SimpleMediator smd = new SimpleMediator();
  23. private List<SimpleColleague> colleagues = new ArrayList<SimpleColleague>();
  24. private SimpleMediator() {
  25. }
  26. public static SimpleMediator getMedium() {
  27. return (smd);
  28. }
  29. public void register(SimpleColleague colleague) {
  30. if (!colleagues.contains(colleague)) {
  31. colleagues.add(colleague);
  32. }
  33. }
  34. public void relay(SimpleColleague scl) {
  35. for (SimpleColleague ob : colleagues) {
  36. if (!ob.equals(scl)) {
  37. ((SimpleColleague) ob).receive();
  38. }
  39. }
  40. }
  41. }
  42. //抽象同事类
  43. interface SimpleColleague {
  44. void receive();
  45. void send();
  46. }
  47. //具体同事类
  48. class SimpleConcreteColleague1 implements SimpleColleague {
  49. SimpleConcreteColleague1() {
  50. SimpleMediator smd = SimpleMediator.getMedium();
  51. smd.register(this);
  52. }
  53. public void receive() {
  54. System.out.println("具体同事类1:收到请求。");
  55. }
  56. public void send() {
  57. SimpleMediator smd = SimpleMediator.getMedium();
  58. System.out.println("具体同事类1:发出请求...");
  59. smd.relay(this); //请中介者转发
  60. }
  61. }
  62. //具体同事类
  63. class SimpleConcreteColleague2 implements SimpleColleague {
  64. SimpleConcreteColleague2() {
  65. SimpleMediator smd = SimpleMediator.getMedium();
  66. smd.register(this);
  67. }
  68. public void receive() {
  69. System.out.println("具体同事类2:收到请求。");
  70. }
  71. public void send() {
  72. SimpleMediator smd = SimpleMediator.getMedium();
  73. System.out.println("具体同事类2:发出请求...");
  74. smd.relay(this); //请中介者转发
  75. }
  76. }
  77. 输出为
  78. 具体同事类1:发出请求...
  79. 具体同事类2:收到请求。
  80. -----------------
  81. 具体同事类2:发出请求...
  82. 具体同事类1:收到请求。

8.迭代器模式

迭代器模式在生活中应用的比较广泛,比如:物流系统中的传送带,不管传送的是什么物品,都会被打包成一个个箱子,并且有一个统一的二维码。这样我们不需要关心箱子里是什么,在分发时只需要一个个检查发送的目的地即可。再比如,我们平时乘坐交通工具,都是统一刷卡或者刷脸进站,而不需要关心是男性还是女性、是残疾人还是正常人等信息。

迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。

也就是说,把东西都放到箱子里面, 你看不到箱子里的东西, 箱子按照一定的顺序排列执行, 这便是迭代器的思想

基本上每个语言都有自己的迭代器,迭代器内部是一个List,很少有情况会让你自己去写迭代器

9.访问者模式

例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量。

访问者模式的核心就时, 对于相同的数据结构内的同一元素, 其访问者不同,同一个元素的响应就不同

这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。

模式的定义与特点

访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式

访问者(Visitor)模式是一种对象行为型模式,其主要优点如下。

  1. 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  2. 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  3. 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  4. 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

访问者(Visitor)模式的主要缺点如下。

  1. 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  2. 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  3. 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

    模式的结构与实现

    访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类,其基本结构与实现方法如下。

    1. 模式的结构

    访问者模式包含以下主要角色。

  4. 抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素。

  5. 具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么。
  6. 抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数。
  7. 具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作。
  8. 对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现。

行为型模式的特点和分类 - 图16

2. 模式的实现

访问者模式的实现代码如下:

  1. package net.biancheng.c.visitor;
  2. import java.util.*;
  3. public class VisitorPattern {
  4. public static void main(String[] args) {
  5. ObjectStructure os = new ObjectStructure();
  6. os.add(new ConcreteElementA());
  7. os.add(new ConcreteElementB());
  8. Visitor visitor = new ConcreteVisitorA();
  9. os.accept(visitor);
  10. System.out.println("------------------------");
  11. visitor = new ConcreteVisitorB();
  12. os.accept(visitor);
  13. }
  14. }
  15. //抽象访问者
  16. interface Visitor {
  17. void visit(ConcreteElementA element);
  18. void visit(ConcreteElementB element);
  19. }
  20. //具体访问者A类
  21. class ConcreteVisitorA implements Visitor {
  22. public void visit(ConcreteElementA element) {
  23. System.out.println("具体访问者A访问-->" + element.operationA());
  24. }
  25. public void visit(ConcreteElementB element) {
  26. System.out.println("具体访问者A访问-->" + element.operationB());
  27. }
  28. }
  29. //具体访问者B类
  30. class ConcreteVisitorB implements Visitor {
  31. public void visit(ConcreteElementA element) {
  32. System.out.println("具体访问者B访问-->" + element.operationA());
  33. }
  34. public void visit(ConcreteElementB element) {
  35. System.out.println("具体访问者B访问-->" + element.operationB());
  36. }
  37. }
  38. //抽象元素类
  39. interface Element {
  40. void accept(Visitor visitor);
  41. }
  42. //具体元素A类
  43. class ConcreteElementA implements Element {
  44. public void accept(Visitor visitor) {
  45. visitor.visit(this);
  46. }
  47. public String operationA() {
  48. return "具体元素A的操作。";
  49. }
  50. }
  51. //具体元素B类
  52. class ConcreteElementB implements Element {
  53. public void accept(Visitor visitor) {
  54. visitor.visit(this);
  55. }
  56. public String operationB() {
  57. return "具体元素B的操作。";
  58. }
  59. }
  60. //对象结构角色
  61. class ObjectStructure {
  62. private List<Element> list = new ArrayList<Element>();
  63. public void accept(Visitor visitor) {
  64. Iterator<Element> i = list.iterator();
  65. while (i.hasNext()) {
  66. ((Element) i.next()).accept(visitor);
  67. }
  68. }
  69. public void add(Element element) {
  70. list.add(element);
  71. }
  72. public void remove(Element element) {
  73. list.remove(element);
  74. }
  75. }
  76. 结果
  77. 具体访问者A访问-->具体元素A的操作。
  78. 具体访问者A访问-->具体元素B的操作。
  79. ------------------------
  80. 具体访问者B访问-->具体元素A的操作。
  81. 具体访问者B访问-->具体元素B的操作。

可以看出, 参与者类里会封装一些方法,然后将其作为入参,传给元素类里面,调用传入的访问者方法, 访问者方法里还有元素自身的方法, 这样就能保证元素自身的那个方法并不会被修改

模式的应用场景

当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。

简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。

通常在以下情况可以考虑使用访问者(Visitor)模式。

  1. 对象结构相对稳定,但其操作算法经常变化的程序。
  2. 对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构。
  3. 对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作。

10.备忘录模式

在日常生活我 我们经常会想要后悔药, 在编程的过程中也是, 有的时候需要我们撤回

在代码中 我们修改了一个对象,由于某些原因我们需要将对象状态回滚到修改之前, 通过备忘录模式便可以实现
备忘录模式又名快照模式

其原理就是将原状态的对象进行保存,存储在某个类中,需要用的时候再拿出来, 这个模式不太常用

模式的定义与特点

备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。

备忘录模式是一种对象行为型模式,其主要优点如下。

  • 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
  • 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
  • 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。

其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。

模式的结构与实现

备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类,现在我们来学习其结构与实现。

1. 模式的结构

备忘录模式的主要角色如下。

  1. 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  2. 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  3. 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改

行为型模式的特点和分类 - 图17

2. 模式的实现

备忘录模式的实现代码如下:

  1. package net.biancheng.c.memento;
  2. public class MementoPattern {
  3. public static void main(String[] args) {
  4. Originator or = new Originator();
  5. Caretaker cr = new Caretaker();
  6. or.setState("S0");
  7. System.out.println("初始状态:" + or.getState());
  8. cr.setMemento(or.createMemento()); //保存状态
  9. or.setState("S1");
  10. System.out.println("新的状态:" + or.getState());
  11. or.restoreMemento(cr.getMemento()); //恢复状态
  12. System.out.println("恢复状态:" + or.getState());
  13. }
  14. }
  15. //备忘录
  16. class Memento {
  17. private String state;
  18. public Memento(String state) {
  19. this.state = state;
  20. }
  21. public void setState(String state) {
  22. this.state = state;
  23. }
  24. public String getState() {
  25. return state;
  26. }
  27. }
  28. //发起人
  29. class Originator {
  30. private String state;
  31. public void setState(String state) {
  32. this.state = state;
  33. }
  34. public String getState() {
  35. return state;
  36. }
  37. public Memento createMemento() {
  38. return new Memento(state);
  39. }
  40. public void restoreMemento(Memento m) {
  41. this.setState(m.getState());
  42. }
  43. }
  44. //管理者
  45. class Caretaker {
  46. private Memento memento;
  47. public void setMemento(Memento m) {
  48. memento = m;
  49. }
  50. public Memento getMemento() {
  51. return memento;
  52. }
  53. }
  54. 结果:
  55. 初始状态:S0
  56. 新的状态:S1
  57. 恢复状态:S0

模式的扩展

在前面介绍的备忘录模式中,有单状态备份的例子,也有多状态备份的例子。下面介绍备忘录模式如何同原型模式混合使用。在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 clone() 方法具有自备份功能,所以,如果让发起人实现 Cloneable 接口就有备份自己的功能,这时可以删除备忘录类,其结构图如图 4 所示。
行为型模式的特点和分类 - 图18

  1. package net.biancheng.c.memento;
  2. public class PrototypeMemento {
  3. public static void main(String[] args) {
  4. OriginatorPrototype or = new OriginatorPrototype();
  5. PrototypeCaretaker cr = new PrototypeCaretaker();
  6. or.setState("S0");
  7. System.out.println("初始状态:" + or.getState());
  8. cr.setMemento(or.createMemento()); //保存状态
  9. or.setState("S1");
  10. System.out.println("新的状态:" + or.getState());
  11. or.restoreMemento(cr.getMemento()); //恢复状态
  12. System.out.println("恢复状态:" + or.getState());
  13. }
  14. }
  15. //发起人原型
  16. class OriginatorPrototype implements Cloneable {
  17. private String state;
  18. public void setState(String state) {
  19. this.state = state;
  20. }
  21. public String getState() {
  22. return state;
  23. }
  24. public OriginatorPrototype createMemento() {
  25. return this.clone();
  26. }
  27. public void restoreMemento(OriginatorPrototype opt) {
  28. this.setState(opt.getState());
  29. }
  30. public OriginatorPrototype clone() {
  31. try {
  32. return (OriginatorPrototype) super.clone();
  33. } catch (CloneNotSupportedException e) {
  34. e.printStackTrace();
  35. }
  36. return null;
  37. }
  38. }
  39. //原型管理者
  40. class PrototypeCaretaker {
  41. private OriginatorPrototype opt;
  42. public void setMemento(OriginatorPrototype opt) {
  43. this.opt = opt;
  44. }
  45. public OriginatorPrototype getMemento() {
  46. return opt;
  47. }
  48. }

11.解释器模式

遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了。

给定一个语言,定义它的语法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子

不太常用