目录

  • 模板方法模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
  • 策略模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 具体策略A的策略方法被访问!
    • 扩展
    • 使用场景
  • 命令模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
    • 使用场景
  • 责任链模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
    • 使用场景
  • 状态模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
    • 使用场景
  • 观察者模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 具体目标发生改变...
    • 扩展
    • 使用场景
  • 中介模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 具体同事类1发出请求。 具体同事类2收到请求。
    • 扩展
    • 具体同事类1:发出请求... 具体同事类2:收到请求。
    • 使用场景
  • 迭代器模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
    • 使用场景
  • 访问者模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 具体访问者A访问-->具体元素A的操作。 具体访问者A访问-->具体元素B的操作。
    • 扩展
    • 使用场景
  • 备忘录模式(快照模式)
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
    • 使用场景
  • 解释器模式
    • 使用背景
    • 定义
    • 特点
    • 结构
    • 实现
    • 扩展
    • 使用场景

行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性
行为型设计模式包含一下十一种,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式:

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

模板方法模式

使用背景

在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关
例如,去银行办理业务一般要经过以下4个流程:取号、排队、办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员进行评分的业务对每个客户是一样的,可以在父类中实现,但是办理具体业务却因人而异,它可能是存款、取款或者转账等,可以延迟到子类中实现
这样的例子在生活中还有很多,例如,一个人每天会起床、吃饭、做事、睡觉等,其中“做事”的内容每天可能不同。我们把这些规定了流程或格式的实例定义成模板,允许使用者根据自己的需求去更新它,例如,简历模板、论文模板、Word 中模板文件等

定义

定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤

特点

该模式的主要优点如下:

  • 它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展。
  • 它在父类中提取了公共的部分代码,便于代码复用。
  • 部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。

该模式的主要缺点如下:

  • 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
  • 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。

结构

模板方法模式包含以下主要角色:

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

    • 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
    • 基本方法:是整个算法中的一个步骤,包含以下几种类型
      • 抽象方法:在抽象类中申明,由具体子类实现
      • 具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它
      • 钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种
  • 具体子类(Concrete Class):实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤

实现

public class TemplateMethodPattern{public static void main(String[] args){AbstractClass tm=new ConcreteClass();tm.TemplateMethod();}
}
//抽象类
public abstract class AbstractClass{//模板方法public void TemplateMethod(){SpecificMethod();abstractMethod1();          abstractMethod2();}  //具体方法public void SpecificMethod(){System.out.println("抽象类中的具体方法被调用...");}   public abstract void abstractMethod1(); //抽象方法1public abstract void abstractMethod2(); //抽象方法2
}
//具体子类
public class ConcreteClass extends AbstractClass{public void abstractMethod1(){System.out.println("抽象方法1的实现被调用...");}   public void abstractMethod2(){System.out.println("抽象方法2的实现被调用...");}
}

运行结果:

抽象类中的具体方法被调用…
抽象方法1的实现被调用…
抽象方法2的实现被调用…

扩展

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

public class HookTemplateMethod{public static void main(String[] args)    {HookAbstractClass tm=new HookConcreteClass();tm.TemplateMethod();}
}
//含钩子方法的抽象类
public abstract class HookAbstractClass{//模板方法public void TemplateMethod(){abstractMethod1();HookMethod1();if(HookMethod2()){SpecificMethod();   }abstractMethod2();}//具体方法public void SpecificMethod(){System.out.println("抽象类中的具体方法被调用...");}//钩子方法1public void HookMethod1(){}//钩子方法2public boolean HookMethod2(){return true;}public abstract void abstractMethod1(); //抽象方法1public abstract void abstractMethod2(); //抽象方法2
}
//含钩子方法的具体子类
public class HookConcreteClass extends HookAbstractClass{public void abstractMethod1(){System.out.println("抽象方法1的实现被调用...");}   public void abstractMethod2(){System.out.println("抽象方法2的实现被调用...");}   public void HookMethod1(){System.out.println("钩子方法1被重写...");}public boolean HookMethod2(){return false;}
}

运行结果:

抽象方法1的实现被调用…
钩子方法1被重写…
抽象方法2的实现被调用…

如果钩子方法 HookMethod1() 和钩子方法 HookMethod2() 的代码改变,则程序的运行结果也会改变
使用场景:
模板方法模式通常适用于以下场景:

  • 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现
  • 当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码
  • 当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展

策略模式

使用背景

在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等,超市促销可以釆用打折、送商品、送积分等方法
在软件开发中也常常遇到类似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能,如数据排序策略有冒泡排序、选择排序、插入排序、二叉树排序等
如果使用多重条件转移语句实现(即硬编码),不但使条件语句变得很复杂,而且增加、删除或更换算法要修改原代码,不易维护,违背开闭原则。如果采用策略模式就能很好解决该问题

定义

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

特点

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

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

其主要缺点如下:

  • 客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类
  • 策略模式造成很多的策略类

结构

策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性,现在我们来分析其基本结构和实现方法
策略模式的主要角色如下:

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

实现

public class StrategyPattern{public static void main(String[] args){Context c=new Context();Strategy s=new ConcreteStrategyA();c.setStrategy(s);c.strategyMethod();System.out.println("-----------------");s=new ConcreteStrategyB();c.setStrategy(s);c.strategyMethod();}
}
//抽象策略类
public interface Strategy{   public void strategyMethod();    //策略方法
}
//具体策略类A
public class ConcreteStrategyA implements Strategy{public void strategyMethod(){System.out.println("具体策略A的策略方法被访问!");}
}
//具体策略类B
public class ConcreteStrategyB implements Strategy{public void strategyMethod(){System.out.println("具体策略B的策略方法被访问!");}
}
//环境类
public class Context{private Strategy strategy;public Strategy getStrategy(){return strategy;}public void setStrategy(Strategy strategy){this.strategy=strategy;}public void strategyMethod(){strategy.strategyMethod();}
}

运行结果:

具体策略A的策略方法被访问!

具体策略B的策略方法被访问!

扩展

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

使用场景

策略模式在很多地方用到,如 Java SE 中的容器布局管理就是一个典型的实例,Java SE 中的每个容器都存在多种布局供用户选择。在程序设计中,通常在以下几种情况中使用策略模式较多:

  • 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
  • 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
  • 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
  • 系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
  • 多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为

命令模式

使用背景

在软件开发系统中,常常出现“方法的请求者”与“方法的实现者”之间存在紧密的耦合关系。这不利于软件功能的扩展与维护。例如,想对行为进行“撤销、重做、记录”等处理都很不方便,因此“如何将方法的请求者与方法的实现者解耦?”变得很重要,命令模式能很好地解决这个问题
在现实生活中,这样的例子也很多,例如,电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者),还有计算机键盘上的“功能键”等

定义

将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行储存、传递、调用、增加与管理

特点

命令模式的主要优点如下:

  • 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦
  • 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活
  • 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令
  • 方便实现 Undo 和 Redo 操作。命令模式可以与下面介绍的备忘录模式结合,实现命令的撤销与恢复

其缺点是:可能产生大量具体命令类。因为计对每一个具体操作都需要设计一个具体命令类,这将增加系统的复杂性

结构

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

  • 抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()
  • 具体命令角色(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作
  • 实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者
  • 调用者/请求者(Invoker)角色:是请求的发送者,它通常拥有很多的命令对象,并通过访问命令对象来执行相关请求,它不直接访问接收者

实现

public class CommandPattern{public static void main(String[] args){Command cmd=new ConcreteCommand();Invoker ir=new Invoker(cmd);System.out.println("客户访问调用者的call()方法...");ir.call();}
}
//调用者
public class Invoker{private Command command;public Invoker(Command command){this.command=command;}public void setCommand(Command command){this.command=command;}public void call(){System.out.println("调用者执行命令command...");command.execute();}
}
//抽象命令
public interface Command{public abstract void execute();
}
//具体命令
public class ConcreteCommand implements Command{private Receiver receiver;ConcreteCommand(){receiver=new Receiver();}public void execute(){receiver.action();}
}
//接收者
public class Receiver{public void action(){System.out.println("接收者的action()方法被调用...");}
}

运行结果:

客户访问调用者的call()方法…
调用者执行命令command…
接收者的action()方法被调用…

扩展

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

程序实现如下所示:

import java.util.ArrayList;
public class CompositeCommandPattern{public static void main(String[] args){AbstractCommand cmd1=new ConcreteCommand1();AbstractCommand cmd2=new ConcreteCommand2();CompositeInvoker ir=new CompositeInvoker();ir.add(cmd1);ir.add(cmd2);System.out.println("客户访问调用者的execute()方法...");ir.execute();}
}
//抽象命令
public interface AbstractCommand{public abstract void execute();
}
//树叶构件: 具体命令1
public class ConcreteCommand1 implements AbstractCommand{private CompositeReceiver receiver;ConcreteCommand1(){receiver=new CompositeReceiver();}public void execute(){       receiver.action1();}
}
//树叶构件: 具体命令2
public class ConcreteCommand2 implements AbstractCommand{private CompositeReceiver receiver;ConcreteCommand2(){receiver=new CompositeReceiver();}public void execute(){       receiver.action2();}
}
//树枝构件: 调用者
public class CompositeInvoker implements AbstractCommand{private ArrayList<AbstractCommand> children = new ArrayList<AbstractCommand>();   public void add(AbstractCommand c){children.add(c);}   public void remove(AbstractCommand c){children.remove(c);}   public AbstractCommand getChild(int i){return children.get(i);}   public void execute(){for(Object obj:children){((AbstractCommand)obj).execute();}}
}
//接收者
public class CompositeReceiver{public void action1(){System.out.println("接收者的action1()方法被调用...");}public void action2(){System.out.println("接收者的action2()方法被调用...");}
}

运行结果:

客户访问调用者的execute()方法…
接收者的action1()方法被调用…
接收者的action2()方法被调用…

命令模式还可以同备忘录(Memento)模式组合使用,这样就变成了可撤销的命令模式,在备忘录模式会有说明

使用场景

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

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

责任链模式

注意:责任链模式也叫职责链模式

使用背景

在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。这样的例子还有很多,如找领导出差报销、生活中的“击鼓传花”游戏等
在计算机软硬件中也有相关例子,如总线网中数据报传送,每台计算机根据目标地址是否同自己的地址相同来决定是否接收;还有异常处理中,处理程序根据异常的类型决定自己是否处理该异常;还有 Struts2 的拦截器、JSP 和 Servlet 的 Filter 等,所有这些,如果用责任链模式都能很好解决

定义

为了避免请求发送者与多个请求处理者耦合在一起,将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,所以责任链将请求的发送者和请求的处理者解耦了

特点

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

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

其主要缺点如下:

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

结构

通常情况下,可以通过数据链表来实现职责链模式的数据结构
职责链模式主要包含以下角色:

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

实现

public class ChainOfResponsibilityPattern{public static void main(String[] args){//组装责任链 Handler handler1=new ConcreteHandler1(); Handler handler2=new ConcreteHandler2(); handler1.setNext(handler2); //提交请求 handler1.handleRequest("two");}
}
//抽象处理者角色
public abstract class Handler{private Handler next;public void setNext(Handler next){this.next=next; }public Handler getNext(){ return next; }   //处理请求的方法public abstract void handleRequest(String request);
}
//具体处理者角色1
public class ConcreteHandler1 extends Handler{public void handleRequest(String request){if(request.equals("one")){System.out.println("具体处理者1负责处理该请求!");       }else{if(getNext()!=null) {getNext().handleRequest(request);             }else{System.out.println("没有人处理该请求!");}} }
}
//具体处理者角色2
public class ConcreteHandler2 extends Handler{public void handleRequest(String request){if(request.equals("two")) {System.out.println("具体处理者2负责处理该请求!");       }else{if(getNext()!=null) {getNext().handleRequest(request);             }else{System.out.println("没有人处理该请求!");}} }
}

运行结果:

具体处理者2负责处理该请求!

扩展

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

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

使用场景

责任链模式通常在以下几种情况使用:

  • 有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定
  • 可动态指定一组对象处理请求,或添加新的处理者
  • 在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求

状态模式

使用背景

在软件开发过程中,应用程序中的有些对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态会发生改变,从而使得其行为也随之发生改变。如人的情绪有高兴的时候和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化
对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 语句来做状态判断,再进行不同情况的处理。但当对象的状态很多时,程序会变得很复杂。而且增加新的状态要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展
以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,放到一系列的状态类当中,这样可以把原来复杂的逻辑判断简单化

定义

对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为

特点

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

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

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

  • 状态模式的使用必然会增加系统的类与对象的个数
  • 状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱

结构

状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法
状态模式包含以下主要角色:

  • 环境(Context)角色:也称为上下文,它定义了客户感兴趣的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为

实现

public class StatePatternClient{public static void main(String[] args){       Context context=new Context();    //创建环境       context.Handle();    //处理请求context.Handle();context.Handle();context.Handle();}
}
//环境类
public class Context{private State state;//定义环境类的初始状态public Context(){this.state=new ConcreteStateA();}//设置新状态public void setState(State state){this.state=state;}//读取状态public State getState(){return(state);}//对请求做处理public void Handle(){state.Handle(this);}
}
//抽象状态类
public abstract class State{public abstract void Handle(Context context);
}
//具体状态A类
public class ConcreteStateA extends State{public void Handle(Context context){System.out.println("当前状态是 A.");context.setState(new ConcreteStateB());}
}
//具体状态B类
public class ConcreteStateB extends State{public void Handle(Context context)    {System.out.println("当前状态是 B.");context.setState(new ConcreteStateA());}
}

运行结果:

当前状态是 A.
当前状态是 B.
当前状态是 A.
当前状态是 B.

扩展

在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,其结构图如下图 所示:

分析:共享状态模式的不同之处是在环境类中增加了一个 HashMap 来保存相关状态,当需要某种状态时可以从中获取,其程序代码如下:

import java.util.HashMap;
public class FlyweightStatePattern{public static void main(String[] args){ShareContext context=new ShareContext(); //创建环境       context.Handle(); //处理请求context.Handle();context.Handle();context.Handle();}
}
//环境类
public class ShareContext{private ShareState state;private HashMap<String, ShareState> stateSet=new HashMap<String, ShareState>();public ShareContext(){state=new ConcreteState1();stateSet.put("1", state);state=new ConcreteState2();stateSet.put("2", state);state=getState("1");}//设置新状态public void setState(ShareState state){this.state=state;}//读取状态public ShareState getState(String key){ShareState s=(ShareState)stateSet.get(key);return s;}//对请求做处理public void Handle(){state.Handle(this);}
}
//抽象状态类
public abstract class ShareState{public abstract void Handle(ShareContext context);
}
//具体状态1类
public class ConcreteState1 extends ShareState{public void Handle(ShareContext context){System.out.println("当前状态是: 状态1");context.setState(context.getState("2"));}
}
//具体状态2类
public class ConcreteState2 extends ShareState{public void Handle(ShareContext context){System.out.println("当前状态是: 状态2");context.setState(context.getState("1"));}
}

运行结果如下:

当前状态是: 状态1
当前状态是: 状态2
当前状态是: 状态1
当前状态是: 状态2

使用场景

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

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

观察者模式

使用背景

在现实世界中,许多对象并不是独立存在的,其中一个对象的行为发生改变可能会导致一个或者多个其他对象的行为也发生改变。例如,某种商品的物价上涨时会导致部分商家高兴,而消费者伤心;还有,当我们开车到交叉路口时,遇到红灯会停,遇到绿灯会行。这样的例子还有很多,例如,股票价格与股民、微信公众号与微信用户、气象局的天气预报与听众、小偷与警察等
在软件世界也是这样,例如,Excel 中的数据与折线图、饼状图、柱状图之间的关系;MVC 模式中的模型与视图的关系;事件模型中的事件源与事件处理者。所有这些,如果用观察者模式来实现就非常方便

定义

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

特点

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

  • 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
  • 目标与观察者之间建立了一套触发机制。

它的主要缺点如下:

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

结构

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

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

实现

import java.util.*;
public class ObserverPattern{public static void main(String[] args){Subject subject=new ConcreteSubject();Observer obs1=new ConcreteObserver1();Observer obs2=new ConcreteObserver2();subject.add(obs1);subject.add(obs2);subject.notifyObserver();}
}
//抽象目标
public abstract class Subject{protected List<Observer> observers=new ArrayList<Observer>();   //增加观察者方法public void add(Observer observer){observers.add(observer);}    //删除观察者方法public void remove(Observer observer){observers.remove(observer);}   public abstract void notifyObserver(); //通知观察者方法
}
//具体目标
public class ConcreteSubject extends Subject{public void notifyObserver(){System.out.println("具体目标发生改变...");System.out.println("--------------");       for(Object obs:observers){((Observer)obs).response();}}
}
//抽象观察者
public interface Observer{void response(); //反应
}
//具体观察者1
public class ConcreteObserver1 implements Observer{public void response(){System.out.println("具体观察者1作出反应!");}
}
//具体观察者1
public class ConcreteObserver2 implements Observer{public void response(){System.out.println("具体观察者2作出反应!");}
}

运行结果:

具体目标发生改变…

具体观察者1作出反应!
具体观察者2作出反应!

扩展

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

  • Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法。
  • void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中。
  • void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update。方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知。
  • void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者。
  1. Observer 接口
  • Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作

使用场景

观察者模式适合以下几种情形:

  • 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象
  • 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用

中介模式

使用背景

在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须告诉其他所有的朋友修改,这叫作“牵一发而动全身”,非常复杂
如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参力口工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙
在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性

定义

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

特点

中介者模式是一种对象行为型模式,其主要优点如下:

  • 降低了对象之间的耦合性,使得对象易于独立地被复用
  • 将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展

其主要缺点是:当同事类太多时,中介者的职责将很大,它会变得复杂而庞大,以至于系统难以维护

结构

中介者模式实现的关键是找出“中介者”,下面对它的结构和实现进行分析
中介者模式包含以下主要角色:

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

实现

import java.util.*;
public class MediatorPattern{public static void main(String[] args){Mediator md=new ConcreteMediator();Colleague c1,c2;c1=new ConcreteColleague1();c2=new ConcreteColleague2();md.register(c1);md.register(c2);c1.send();System.out.println("-------------");c2.send();}
}
//抽象中介者
public abstract class Mediator{public abstract void register(Colleague colleague);public abstract void relay(Colleague cl); //转发
}
//具体中介者
public class ConcreteMediator extends Mediator{private List<Colleague> colleagues=new ArrayList<Colleague>();public void register(Colleague colleague){if(!colleagues.contains(colleague)){colleagues.add(colleague);colleague.setMedium(this);}}public void relay(Colleague cl){for(Colleague ob:colleagues){if(!ob.equals(cl)){((Colleague)ob).receive();}   }}
}
//抽象同事类
public abstract class Colleague{protected Mediator mediator;public void setMedium(Mediator mediator){this.mediator=mediator;}   public abstract void receive();   public abstract void send();
}
//具体同事类
public class ConcreteColleague1 extends Colleague{public void receive(){System.out.println("具体同事类1收到请求。");}   public void send(){System.out.println("具体同事类1发出请求。");mediator.relay(this); //请中介者转发}
}
//具体同事类
public class ConcreteColleague2 extends Colleague{public void receive(){System.out.println("具体同事类2收到请求。");}   public void send(){System.out.println("具体同事类2发出请求。");mediator.relay(this); //请中介者转发}
}

运行结果:

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

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

扩展

在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单:

  • 不定义中介者接口,把具体中介者对象实现成为单例
  • 同事对象不持有中介者,而是在需要的时f矣直接获取中介者对象并调用

程序代码如下:

import java.util.*;
public class SimpleMediatorPattern{public static void main(String[] args){SimpleColleague c1,c2;c1=new SimpleConcreteColleague1();c2=new SimpleConcreteColleague2();c1.send();System.out.println("-----------------");c2.send();}
}
//简单单例中介者
public class SimpleMediator{private static SimpleMediator smd=new SimpleMediator();   private List<SimpleColleague> colleagues=new ArrayList<SimpleColleague>();   private SimpleMediator(){}   public static SimpleMediator getMedium(){return(smd);}public void register(SimpleColleague colleague){if(!colleagues.contains(colleague)){colleagues.add(colleague);}}public void relay(SimpleColleague scl){       for(SimpleColleague ob:colleagues){if(!ob.equals(scl)){((SimpleColleague)ob).receive();}   }}
}
//抽象同事类
public interface SimpleColleague{void receive();   void send();
}
//具体同事类
public class SimpleConcreteColleague1 implements SimpleColleague{SimpleConcreteColleague1(){SimpleMediator smd=SimpleMediator.getMedium();smd.register(this);}public void receive(){System.out.println("具体同事类1:收到请求。");}   public void send(){SimpleMediator smd=SimpleMediator.getMedium();System.out.println("具体同事类1:发出请求...");smd.relay(this); //请中介者转发}
}
//具体同事类
public class SimpleConcreteColleague2 implements SimpleColleague{SimpleConcreteColleague2(){SimpleMediator smd=SimpleMediator.getMedium();smd.register(this);}public void receive(){System.out.println("具体同事类2:收到请求。");}   public void send(){SimpleMediator smd=SimpleMediator.getMedium();System.out.println("具体同事类2:发出请求...");smd.relay(this); //请中介者转发}
}

运行结果:

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

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

使用场景

下面分析其以下应用场景:

  • 当对象之间存在复杂的网状结构关系而导致依赖关系混乱且难以复用时
  • 当想创建一个运行于多个类之间的对象,又不想生成新的子类时

迭代器模式

使用背景

在现实生活以及程序设计中,经常要访问一个聚合对象中的各个元素,如“数据结构”中的链表遍历,通常的做法是将链表的创建和遍历都放在同一个类中,但这种方式不利于程序的扩展,如果要更换遍历方法就必须修改程序源代码,这违背了 “开闭原则”
既然将遍历方法封装在聚合类中不可取,那么聚合类中不提供遍历方法,将遍历方法由用户自己实现是否可行呢?答案是同样不可取,因为这种方式会存在两个缺点:

  • 暴露了聚合类的内部表示,使其数据不安全
  • 增加了客户的负担

“迭代器模式”能较好地克服以上缺点,它在客户访问类与聚合类之间插入一个迭代器,这分离了聚合对象与其遍历行为,对客户也隐藏了其内部细节,且满足“单一职责原则”和“开闭原则”,如 Java 中的 Collection、List、Set、Map 等都包含了迭代器

定义

提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示

特点

迭代器模式是一种对象行为型模式,其主要优点如下:

  • 访问一个聚合对象的内容而无须暴露它的内部表示
  • 遍历任务交由迭代器完成,这简化了聚合类
  • 它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历
  • 增加新的聚合类和迭代器类都很方便,无须修改原有代码
  • 封装性良好,为遍历不同的聚合结构提供一个统一的接口

其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性

结构

迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。现在我们来分析其基本结构与实现方法
迭代器模式主要包含以下角色:

  • 抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口
  • 具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例
  • 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法
  • 具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置

实现

import java.util.*;
public class IteratorPattern{public static void main(String[] args){Aggregate ag=new ConcreteAggregate(); ag.add("中山大学"); ag.add("华南理工"); ag.add("韶关学院");System.out.print("聚合的内容有:");Iterator it=ag.getIterator(); while(it.hasNext()){ Object ob=it.next(); System.out.print(ob.toString()+"\t"); }Object ob=it.first();System.out.println("\nFirst:"+ob.toString());}
}
//抽象聚合
public interface Aggregate{ public void add(Object obj); public void remove(Object obj); public Iterator getIterator();
}
//具体聚合
public class ConcreteAggregate implements Aggregate{ private List<Object> list=new ArrayList<Object>(); public void add(Object obj){ list.add(obj); }public void remove(Object obj){ list.remove(obj); }public Iterator getIterator(){ return(new ConcreteIterator(list)); }
}
//抽象迭代器
public interface Iterator{Object first();Object next();boolean hasNext();
}
//具体迭代器
public class ConcreteIterator implements Iterator{ private List<Object> list=null; private int index=-1; public ConcreteIterator(List<Object> list){ this.list=list; } public boolean hasNext(){ if(index<list.size()-1){ return true;}else{return false;}}public Object first(){index=0;Object obj=list.get(index);;return obj;}public Object next(){ Object obj=null; if(this.hasNext()){ obj=list.get(++index); } return obj; }
}

运行结果:

聚合的内容有:中山大学 华南理工 韶关学院
First:中山大学

扩展

迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问,其结构图如下图所示:

使用场景

迭代器模式通常在以下几种情况使用:

  • 当需要为聚合对象提供多种遍历方式时
  • 当需要为遍历不同的聚合结构提供一个统一的接口时
  • 当访问一个聚合对象的内容而无须暴露其内部细节的表示时

由于聚合与迭代器的关系非常密切,所以大多数语言在实现聚合类时都提供了迭代器类,因此大数情况下使用语言中已有的聚合类的迭代器就已经够了

访问者模式

使用背景

在现实生活中,有些集合对象中存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。例如,公园中存在多个景点,也存在多个游客,不同的游客对同一个景点的评价可能不同;医院医生开的处方单中包含多种药元素,査看它的划价员和药房工作人员对它的处理方式也不同,划价员根据处方单上面的药品名和数量进行划价,药房工作人员根据处方单的内容进行抓药
这样的例子还有很多,例如,电影或电视剧中的人物角色,不同的观众对他们的评价也不同;还有顾客在商场购物时放在“购物车”中的商品,顾客主要关心所选商品的性价比,而收银员关心的是商品的价格和数量
这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性

定义

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式

特点

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

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

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

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

结构

访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类
访问者模式包含以下主要角色:

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

实现

import java.util.*;
public class VisitorPattern{public static void main(String[] args){ObjectStructure os=new ObjectStructure();os.add(new ConcreteElementA());os.add(new ConcreteElementB());Visitor visitor=new ConcreteVisitorA();os.accept(visitor);System.out.println("------------------------");visitor=new ConcreteVisitorB();os.accept(visitor);}
}
//抽象访问者
public interface Visitor{void visit(ConcreteElementA element);void visit(ConcreteElementB element);
}
//具体访问者A类
public class ConcreteVisitorA implements Visitor{public void visit(ConcreteElementA element){System.out.println("具体访问者A访问-->"+element.operationA());}public void visit(ConcreteElementB element){System.out.println("具体访问者A访问-->"+element.operationB());}
}
//具体访问者B类
public class ConcreteVisitorB implements Visitor{public void visit(ConcreteElementA element){System.out.println("具体访问者B访问-->"+element.operationA());}public void visit(ConcreteElementB element){System.out.println("具体访问者B访问-->"+element.operationB());}
}
//抽象元素类
public interface Element{void accept(Visitor visitor);
}
//具体元素A类
public class ConcreteElementA implements Element{public void accept(Visitor visitor){visitor.visit(this);}public String operationA(){return "具体元素A的操作。";}
}
//具体元素B类
public class ConcreteElementB implements Element{public void accept(Visitor visitor){visitor.visit(this);}public String operationB(){return "具体元素B的操作。";}
}
//对象结构角色
public class ObjectStructure{   private List<Element> list=new ArrayList<Element>();   public void accept(Visitor visitor){Iterator<Element> i=list.iterator();while(i.hasNext()){((Element) i.next()).accept(visitor);}      }public void add(Element element){list.add(element);}public void remove(Element element){list.remove(element);}
}

运行结果:

具体访问者A访问–>具体元素A的操作。
具体访问者A访问–>具体元素B的操作。

具体访问者B访问–>具体元素A的操作。
具体访问者B访问–>具体元素B的操作。

扩展

访问者(Visitor)模式是使用频率较高的一种设计模式,它常常同以下两种设计模式联用。

  1. 与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。如对象结构是用 List 实现的,它通过 List 对象的 Itemtor() 方法获取迭代器。如果对象结构中的聚合类没有提供迭代器,也可以用迭代器模式自定义一个
  2. 访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式,其结构图如下图所示:

使用场景

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

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

备忘录模式(快照模式)

使用背景

每个人都有犯错误的时候,都希望有种“后悔药”能弥补自己的过失,让自己重新开始,但现实是残酷的。在计算机应用中,客户同样会常常犯错误,能否提供“后悔药”给他们呢?当然是可以的,而且是有必要的。这个功能由“备忘录模式”来实现
其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类
备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态

定义

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

特点

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

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

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

结构

备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类
备忘录模式的主要角色如下:

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

实现

public class MementoPattern{public static void main(String[] args){Originator or=new Originator();Caretaker cr=new Caretaker();       or.setState("S0"); System.out.println("初始状态:"+or.getState());           cr.setMemento(or.createMemento()); //保存状态      or.setState("S1"); System.out.println("新的状态:"+or.getState());        or.restoreMemento(cr.getMemento()); //恢复状态System.out.println("恢复状态:"+or.getState());}
}
//备忘录
public class Memento{ private String state; public Memento(String state){ this.state=state; }     public void setState(String state){ this.state=state; }public String getState(){ return state; }
}
//发起人
public class Originator{ private String state;     public void setState(String state){ this.state=state; }public String getState(){ return state; }public Memento createMemento(){ return new Memento(state); } public void restoreMemento(Memento m){ this.setState(m.getState()); }
}
//管理者
public class Caretaker{ private Memento memento;       public void setMemento(Memento m){ memento=m; }public Memento getMemento(){ return memento; }
}

运行结果:

初始状态:S0
新的状态:S1
恢复状态:S0

扩展

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

实现代码如下:

public class PrototypeMemento{public static void main(String[] args){OriginatorPrototype or=new OriginatorPrototype();PrototypeCaretaker cr=new PrototypeCaretaker();       or.setState("S0"); System.out.println("初始状态:"+or.getState());           cr.setMemento(or.createMemento()); //保存状态      or.setState("S1"); System.out.println("新的状态:"+or.getState());        or.restoreMemento(cr.getMemento()); //恢复状态System.out.println("恢复状态:"+or.getState());}
}
//发起人原型
public class OriginatorPrototype  implements Cloneable{ private String state;     public void setState(String state){ this.state=state; }public String getState(){ return state; }public OriginatorPrototype createMemento(){ return this.clone(); } public void restoreMemento(OriginatorPrototype opt){ this.setState(opt.getState()); }public OriginatorPrototype clone(){try{return (OriginatorPrototype) super.clone();} catch(CloneNotSupportedException e) {e.printStackTrace();}return null;}
}
//原型管理者
public class PrototypeCaretaker{ private OriginatorPrototype opt;       public void setMemento(OriginatorPrototype opt){ this.opt=opt; }public OriginatorPrototype getMemento(){ return opt; }
}

运行结果:

初始状态:S0
新的状态:S1
恢复状态:S0

使用场景

该模式的应用场景:

  • 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能
  • 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作

解释器模式

使用背景

在软件开发中,会遇到有些问题多次重复出现,而且有一定的相似性和规律性。如果将它们归纳成一种简单的语言,那么这些问题实例将是该语言的一些句子,这样就可以用“编译原理”中的解释器模式来实现了
虽然使用解释器模式的实例不是很多,但对于满足以上特点,且对运行效率要求不是很高的应用实例,如果用解释器模式来实现,其效果是非常好的,本文将介绍其工作原理与使用方法

定义

给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文
这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。例如,汉语中的句子有很多,“我是中国人”是其中的一个句子,可以用一棵语法树来直观地描述语言中的句子

特点

解释器模式是一种类行为型模式,其主要优点如下:

  • 扩展性好。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法
  • 容易实现。在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易

解释器模式的主要缺点如下:

  • 执行效率较低。解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦
  • 会引起类膨胀。解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护
  • 可应用的场景比较少。在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到

结构

解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念

    1. 文法

文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一
条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。例如,中文中的“句子”的文法如下。
〈句子〉::=〈主语〉〈谓语〉〈宾语〉 〈主语〉::=〈代词〉|〈名词〉 〈谓语〉::=〈动词〉 〈宾语〉::=〈代词〉|〈名词〉 〈代词〉你|我|他 〈名词〉7大学生I筱霞I英语 〈动词〉::=是|学习
注:这里的符号“::=”表示“定义为”的意思,用“〈”和“〉”括住的是非终结符,没有括住的是终结符。

    1. 句子

句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。例如,上述文法可以推出“我是大学生”,所以它是句子

    1. 语法树

语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。图 下图所示是“我是大学生”的语法树

句子“我是大学生”的语法树
有了以上基础知识,现在来介绍解释器模式的结构就简单了。解释器模式的结构与组合模式相似,不过其包含的组成元素比组合模式多,而且组合模式是对象结构型模式,而解释器模式是类行为型模式
解释器模式包含以下主要角色:

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()
  • 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法

实现

//抽象表达式类
public interface AbstractExpression{public Object interpret(String info);    //解释方法
}
//终结符表达式类
public class TerminalExpression implements AbstractExpression{public Object interpret(String info){//对终结符表达式的处理}
}
//非终结符表达式类
public class NonterminalExpression implements AbstractExpression{private AbstractExpression exp1;private AbstractExpression exp2;public Object interpret(String info){//非对终结符表达式的处理}
}
//环境类
public class Context{private AbstractExpression exp;public Context(){//数据初始化}public void operation(String info){//调用相关表达式类的解释方法}
}

扩展

在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,Java 提供了以下强大的数学公式解析器:Expression4J、MESP(Math Expression String Parser) 和 Jep 等,它们可以解释一些复杂的文法,功能强大,使用简单
现在以 Jep 为例来介绍该工具包的使用方法。Jep 是 Java expression parser 的简称,即 Java 表达式分析器,它是一个用来转换和计算数学表达式的 Java 库。通过这个程序库,用户可以以字符串的形式输入一个任意的公式,然后快速地计算出其结果。而且 Jep 支持用户自定义变量、常量和函数,它包括许多常用的数学函数和常量
使用前先下载 Jep 压缩包,解压后,将 jep-x.x.x.jar 文件移到选择的目录中,在 Eclipse 的“Java 构建路径”对话框的“库”选项卡中选择“添加外部 JAR(X)…”,将该 Jep 包添加项目中后即可使用其中的类库
下面以计算存款利息为例来介绍。存款利息的计算公式是:本金x利率x时间=利息,其相关代码如下:

import com.singularsys.jep.*;
public class JepDemo{public static void main(String[] args) throws JepException{Jep jep=new Jep();//定义要计算的数据表达式String 存款利息="本金*利率*时间";//给相关变量赋值jep.addVariable("本金",10000);jep.addVariable("利率",0.038);jep.addVariable("时间",2);jep.parse(存款利息);    //解析表达式Object accrual=jep.evaluate();    //计算System.out.println("存款利息:"+accrual);}
}

运行结果:

存款利息:760.0

使用场景

它的应用场景:

  • 当语言的文法较为简单,且执行效率不是关键问题时
  • 当问题重复出现,且可以用一种简单的语言来进行表达时
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释

注意:解释器模式在实际的软件开发中使用比较少,因为它会引起效率、性能以及维护等问题。如果碰到对表达式的解释,在 Java 中可以用 Expression4J 或 Jep 等来设计

1-1-5、行为型设计模式相关推荐

  1. 技术图文:02 创建型设计模式(下)

    创建型设计模式(下) 知识结构: 图1 知识结构 单例模式 – 确保对象的唯一性 Sunny 软件公司承接了一个服务器负载均衡软件的开发工作,该软件运行在一台负载均衡服务器上,可以将并发访问和数据流量 ...

  2. 技术图文:02 创建型设计模式(上)

    创建型设计模式(上) 知识结构: 图1 知识结构 简单工厂模式 Sunny 软件公司欲基于 C# 语言开发一套图表库,该图表库可以为应用系统提供各种不同外观的图表,如: 柱状图(histogram) ...

  3. 中介者模式 调停者 Mediator 行为型 设计模式(二十一)

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

  4. 模板方法模式 Template method 行为型 设计模式(二十六)

    模板方法模式 Template method 上图为网上百度的一份简历模板截图 相信大家都有求职的经历,那么必然需要简历,写简历的时候,很可能你会网上检索一份简历模板,使用此模板的格式,然后替换为你的 ...

  5. 创建型设计模式对比总结 设计模式(八)

    创建型模式是new 的一种替代方式,可以将对象的创建与具体的类型进行分离 目前已经介绍了5种创建型设计模式(如果简单工厂算一种的话,那就是6种) 分别是: 简单工厂模式.工厂方法模式.抽象工厂模式.建 ...

  6. java面向对象程序设计第三版_JAVA面向对象程序设计之创建型设计模式

    [本文详细介绍了JAVA面向对象程序设计中的创建型设计模式,欢迎读者朋友们阅读.转发和收藏!] 1 基本概念 1.1 什么是设计模式 设计模式( Design pattern )是一套被反复使用.多数 ...

  7. 观察者模式 Observer 发布订阅模式 源 监听 行为型 设计模式(二十三)

    观察者模式 Observer 意图 定义对象一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖他的对象都得到通知并自动更新. 别名:依赖(Dependents),发布订阅(Publish-Su ...

  8. 《Python编程实战:运用设计模式、并发和程序库创建高质量程序》—— 第1章 Python的创建型设计模式...

    本节书摘来自华章出版社<Python编程实战:运用设计模式.并发和程序库创建高质量程序>一 书中的第1章,第1.1节,作者:(美) Mark Summerfield,更多章节内容可以访问云 ...

  9. 从框架源码中学习创建型设计模式

    文章目录 从框架源码中解读创建型设计模式 工厂模式 案例一:RocketMQ源码-创建Producer生产者 案例二:RocketMQ源码-创建过滤器工厂 抽象工厂 案例一:Dubbo源码-创建缓存的 ...

  10. java设计模式之行为型设计模式

    行为型设计模式: 责任链(Chain of Responsibility): 用途:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求 ...

最新文章

  1. 【深度学习基础】一步一步讲解卷积神经网络
  2. leetcode--Two Sum(2)
  3. HDU 3001 Travelling
  4. 深圳内推 | ​IDEA数字经济研究院招聘NLP算法工程师/算法实习生
  5. 也来谈谈RNN的梯度消失/爆炸问题
  6. strcpy()源代码
  7. C#中A a=new B()的意义
  8. opend和open的区别_open与open up的区别
  9. C# winfrom单击事件弹出浏览器
  10. 多元回归的拟合优度:R^2
  11. SAS的win10 64位安装过程
  12. 单维度量表验证性因子分析_验证性因子分析.ppt
  13. 常用的php函数,PHP 常用函数汇总
  14. 1750. 救生员 Lifeguards S
  15. 团队作业-Beta冲刺(周四)
  16. 幸运数47:给定正整数n,求不大于n的仅由4和7组成的数字的个数
  17. 【图像配准】基于灰度的模板匹配算法(三):划分强度一致法(PIU)
  18. Mac应用无法打开或文件损坏的处理方法
  19. 自创小说《彩虹雨》 连载(四)
  20. 【转载】PTN与IPRAN承载LTE的比较

热门文章

  1. 大数据图数据库之TAO数据库
  2. 数据交换技术:OPC技术工作原理简述
  3. Web Services Ksoap 下载多张图片
  4. 点云深度学习:多一维看世界【VALSE Webinar】Panel实录
  5. python爬虫——爬去淘宝商品页面,总是跳转到登录界面
  6. java web 题_javaWeb习题与答案
  7. nodejs stream 经典解析
  8. 数据挖掘-决策树算法的R实现
  9. 杂谈:编程解决水管工游戏
  10. CSS实现空心三角指示箭头