设计模式06——行为型模式

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

一、模板方法模式
 在面向对象程序设计过程中,程序员常常会遇到这种情况:设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
 模板方法(Template Method)模式的定义如下:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
 该模式的主要优点如下:
  1、它封装了不变部分,扩展可变部分。它把认为是不变部分的算法封装到父类中实现,而把可变部分算法由子类继承实现,便于子类继续扩展
  2、它在父类中提取了公共的部分代码,便于代码复用
  3、部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则
 该模式的主要缺点如下:
  1、对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度
  2、父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度
  3、由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍
 模板方法的结构
  模板方法模式需要注意抽象类与具体子类之间的协作。它用到了虚函数的多态性技术以及“不用调用我,让我来调用你”的反向控制技术。
 1)抽象类/抽象模板(Abstract Class)
 抽象模板类,负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。这些方法的定义如下:
  ① 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
  ② 基本方法:是整个算法中的一个步骤,包含以下几种类型
   a、抽象方法:在抽象类中声明,由具体子类实现
   b、具体方法:在抽象类中已经实现,在具体子类中可以继承或重写它
   c、钩子方法:在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种
 2)具体子类/具体实现(Concrete Class)
 具体实现类,实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的一个组成步骤。

在这里插入图片描述

public class TemplateMethodPattern {
	public static void main(String[] args) {
		AbstractClass tm = new ConcreteClass();
		tm.TemplateMethod();
	}
}

//抽象类
abstract class AbstractClass {
	// 模板方法
	public void TemplateMethod() {
		SpecificMethod();
		abstractMethod1();
		abstractMethod2();
	}

	// 具体方法
	public void SpecificMethod() {
		System.out.println("抽象类中的具体方法被调用...");
	}

	// 抽象方法1
	public abstract void abstractMethod1();

	// 抽象方法2
	public abstract void abstractMethod2();
}

//具体子类
class ConcreteClass extends AbstractClass {
	public void abstractMethod1() {
		System.out.println("抽象方法1的实现被调用...");
	}

	public void abstractMethod2() {
		System.out.println("抽象方法2的实现被调用...");
	}
}

 模板方法的应用场景
  1、算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现
  2、当多个子类存在公共的行为时,可以将其提取出来并集中到一个公共父类中以避免代码重复。首先,要识别现有代码中的不同之处,并且将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码
  3、当需要控制子类的扩展时,模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展
 模板方法的扩展
  在模板方法模式中,基本方法包含:抽象方法、具体方法和钩子方法,正确使用“钩子方法”可以使得子类控制父类的行为。如下面例子中,可以通过在具体子类中重写钩子方法 HookMethod1() 和 HookMethod2() 来改变抽象父类中的运行结果。
在这里插入图片描述

public class HookTemplateMethod {
	public static void main(String[] args) {
		HookAbstractClass tm = new HookConcreteClass();
		tm.TemplateMethod();
	}
}

//含钩子方法的抽象类
abstract class HookAbstractClass {
	// 模板方法
	public void TemplateMethod() {
		abstractMethod1();
		HookMethod1();
		if (HookMethod2()) {
			SpecificMethod();
		}
		abstractMethod2();
	}

	// 具体方法
	public void SpecificMethod() {
		System.out.println("抽象类中的具体方法被调用...");
	}

	// 钩子方法1
	public void HookMethod1() {
	}

	// 钩子方法2
	public boolean HookMethod2() {
		return true;
	}

	// 抽象方法1
	public abstract void abstractMethod1();

	// 抽象方法2
	public abstract void abstractMethod2();
}

//含钩子方法的具体子类
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;
	}
}

二、策略模式
 策略(Strategy)模式的定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
 策略模式的主要优点如下:
  1、多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句,如 if…else 语句、switch…case 语句
  2、策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码
  3、策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的实现
  4、策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法
  5、策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离
 其主要缺点如下:
  1、客户端必须理解所有策略算法的区别,以便适时选择恰当的算法类
  2、策略模式造成很多的策略类,增加维护难度
 策略模式的结构
 策略模式是准备一组算法,并将这组算法封装到一系列的策略类里面,作为一个抽象策略类的子类。策略模式的重心不是如何实现算法,而是如何组织这些算法,从而让程序结构更加灵活,具有更好的维护性和扩展性。
 策略模式的主要角色如下:
  1、抽象策略(Strategy)类:定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,环境角色使用这个接口调用不同的算法,一般使用接口或抽象类实现
  2、具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现
  3、环境(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();
	}
}

//抽象策略类
interface Strategy {
	public void strategyMethod(); // 策略方法
}

//具体策略类A
class ConcreteStrategyA implements Strategy {
	public void strategyMethod() {
		System.out.println("具体策略A的策略方法被访问!");
	}
}

//具体策略类B
class ConcreteStrategyB implements Strategy {
	public void strategyMethod() {
		System.out.println("具体策略B的策略方法被访问!");
	}
}

//环境类
class Context {
	private Strategy strategy;

	public Strategy getStrategy() {
		return strategy;
	}

	public void setStrategy(Strategy strategy) {
		this.strategy = strategy;
	}

	public void strategyMethod() {
		strategy.strategyMethod();
	}
}

 策略模式的应用场景
  1、一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
  2、一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句
  3、系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时
  4、系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构
  5、多个类只区别在表现行为不同,可以使用策略模式,在运行时动态选择具体要执行的行为
 策略工厂模式
在这里插入图片描述
三、命令模式
 命令(Command)模式的定义如下:将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,方便将命令对象进行储存、传递、调用、增加与管理。
 在现实生活中,命令模式的例子也很多。比如看电视时,我们只需要轻轻一按遥控器就能完成频道的切换,这就是命令模式,将换台请求和换台处理完全解耦了。电视机遥控器(命令发送者)通过按钮(具体命令)来遥控电视机(命令接收者)。
 命令模式的主要优点如下:
  1、通过引入中间件(抽象接口)降低系统的耦合度
  2、扩展性良好,增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,且满足“开闭原则”
  3、可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令
  4、方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复
  5、可以在现有命令的基础上,增加额外功能。比如日志记录,结合装饰器模式会更加灵活
 其缺点是:
  1、可能产生大量具体的命令类。因为每一个具体操作都需要设计一个具体命令类,这会增加系统的复杂性
  2、命令模式的结果其实就是接收方的执行结果,但是为了以命令的形式进行架构、解耦请求与实现,引入了额外类型结构(引入了请求方与抽象命令接口),增加了理解上的困难。不过这也是设计模式的通病,抽象必然会额外增加类的数量,代码抽离肯定比代码聚合更加难理解
 命令模式的结构
 命令模式包含以下主要角色:
  1、抽象命令类(Command)角色:声明执行命令的接口,拥有执行命令的抽象方法 execute()
  2、具体命令类(Concrete Command)角色:是抽象命令类的具体实现类,它拥有接收者对象,并通过调用接收者的功能来完成命令要执行的操作
  3、实现者/接收者(Receiver)角色:执行命令功能的相关操作,是具体命令对象业务的真正实现者
  4、调用者/请求者(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();
	}
}

//调用者
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();
	}
}

//抽象命令
interface Command {
	public abstract void execute();
}

//具体命令
class ConcreteCommand implements Command {
	private Receiver receiver;

	ConcreteCommand() {
		receiver = new Receiver();
	}

	public void execute() {
		receiver.action();
	}
}

//接收者
class Receiver {
	public void action() {
		System.out.println("接收者的action()方法被调用...");
	}
}

 命令模式的应用场景
  当系统的某项操作具备命令语义,且命令实现不稳定(变化)时,可以通过命令模式解耦请求与实现。使用抽象命令接口使请求方的代码架构稳定,封装接收方具体命令的实现细节。接收方与抽象命令呈现弱耦合(内部方法无需一致),具备良好的扩展性。通常适用于以下场景:
  1、请求调用者需要与请求接收者解耦时,命令模式可以使调用者和接收者不直接交互
  2、系统随机请求命令或经常增加、删除命令时,命令模式可以方便地实现这些功能
  3、当系统需要执行一组操作时,命令模式可以定义宏命令来实现该功能
  4、当系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作时,可以将命令对象存储起来,采用备忘录模式来实现
 宏命令模式(组合命令模式)
  在软件开发中,有时将命令模式与前面学的组合模式联合使用,这就构成了宏命令模式,也叫组合命令模式。宏命令包含了一组命令,它充当了具体命令与调用者的双重角色,执行它时将递归调用它所包含的所有命令。
在这里插入图片描述

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();
	}
}

//抽象命令
interface AbstractCommand {
	public abstract void execute();
}

//树叶构件: 具体命令1
class ConcreteCommand1 implements AbstractCommand {
	private CompositeReceiver receiver;

	ConcreteCommand1() {
		receiver = new CompositeReceiver();
	}

	public void execute() {
		receiver.action1();
	}
}

//树叶构件: 具体命令2
class ConcreteCommand2 implements AbstractCommand {
	private CompositeReceiver receiver;

	ConcreteCommand2() {
		receiver = new CompositeReceiver();
	}

	public void execute() {
		receiver.action2();
	}
}

//树枝构件: 调用者
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();
		}
	}
}

//接收者
class CompositeReceiver {
	public void action1() {
		System.out.println("接收者的action1()方法被调用...");
	}

	public void action2() {
		System.out.println("接收者的action2()方法被调用...");
	}
}

四、职责链模式(责任链模式)
 责任链(Chain of Responsibility)模式的定义:为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
 在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
 责任链模式是一种对象行为型模式,其主要优点如下:
  1、降低了对象之间的耦合度。该模式使得一个对象无须知道到底是哪一个对象处理其请求以及链的结构,发送者和接收者也无须拥有对方的明确信息
  2、增强了系统的可扩展性。可以根据需要增加新的请求处理类,满足开闭原则
  3、增强了给对象指派职责的灵活性。当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任
  4、责任链简化了对象之间的连接。每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句
  5、责任分担。每个类只需要处理自己该处理的工作,不该处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则
 其主要缺点如下:
  1、不能保证每个请求一定被处理。由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理
  2、对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响
  3、职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用
 模式的结构
  通常情况下,可以通过数据链表来实现职责链模式。
 责任链模式通常包含以下角色:
  1、抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接
  2、具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者
  3、客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
 责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
在这里插入图片描述
在这里插入图片描述

public class ChainOfResponsibilityPattern {
	public static void main(String[] args) {
		// 组装责任链
		Handler handler1 = new ConcreteHandler1();
		Handler handler2 = new ConcreteHandler2();
		handler1.setNext(handler2);
		// 提交请求
		handler1.handleRequest("two");
	}
}

//抽象处理者角色
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
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
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("没有人处理该请求!");
			}
		}
	}
}

 应用场景:
  1、多个对象可以处理一个请求,但具体由哪个对象处理该请求在运行时自动确定
  2、可动态指定一组对象处理请求,或添加新的处理者
  3、需要在不明确指定请求处理者的情况下,向多个处理者中的一个提交请求
 职责链模式存在以下两种情况:
  1、纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理
  2、不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收

五、状态模式
 在软件开发过程中,应用程序中的部分对象可能会根据不同的情况做出不同的行为,我们把这种对象称为有状态的对象,而把影响对象行为的一个或多个动态变化的属性称为状态。当有状态的对象与外部事件产生互动时,其内部状态就会发生改变,从而使其行为也发生改变。如人都有高兴和伤心的时候,不同的情绪有不同的行为,当然外界也会影响其情绪变化。对这种有状态的对象编程,传统的解决方案是:将这些所有可能发生的情况全都考虑到,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。但是显然这种做法对复杂的状态判断存在天然弊端,条件判断语句会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
 以上问题如果采用“状态模式”就能很好地得到解决。状态模式的解决思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
 状态(State)模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
 状态模式是一种对象行为型模式,其主要优点如下:
  1、结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”
  2、将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖
  3、状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换
 状态模式的主要缺点如下:
  1、状态模式的使用必然会增加系统的类与对象的个数
  2、状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱
  3、状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码
 状态模式的结构
  状态模式把受环境改变的对象行为包装在不同的状态对象里,其意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
 状态模式包含以下主要角色:
  1、环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换
  2、抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为
  3、具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换
在这里插入图片描述

public class StatePatternClient {
	public static void main(String[] args) {
		Context context = new Context(); // 创建环境
		context.Handle(); // 处理请求
		context.Handle();
		context.Handle();
		context.Handle();
	}
}

//环境类
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);
	}
}

//抽象状态类
abstract class State {
	public abstract void Handle(Context context);
}

//具体状态A类
class ConcreteStateA extends State {
	public void Handle(Context context) {
		System.out.println("当前状态是 A.");
		context.setState(new ConcreteStateB());
	}
}

//具体状态B类
class ConcreteStateB extends State {
	public void Handle(Context context) {
		System.out.println("当前状态是 B.");
		context.setState(new ConcreteStateA());
	}
}

 示例:用“状态模式”设计一个多线程的状态转换程序
在这里插入图片描述

public class ThreadStateTest {
	public static void main(String[] args) {
		ThreadContext context = new ThreadContext();
		context.start();
		context.getCPU();
		context.suspend();
		context.resume();
		context.getCPU();
		context.stop();
	}
}

//环境类
class ThreadContext {
	private ThreadState state;

	ThreadContext() {
		state = new New();
	}

	public void setState(ThreadState state) {
		this.state = state;
	}

	public ThreadState getState() {
		return state;
	}

	public void start() {
		((New) state).start(this);
	}

	public void getCPU() {
		((Runnable) state).getCPU(this);
	}

	public void suspend() {
		((Running) state).suspend(this);
	}

	public void stop() {
		((Running) state).stop(this);
	}

	public void resume() {
		((Blocked) state).resume(this);
	}
}

//抽象状态类:线程状态
abstract class ThreadState {
	protected String stateName; // 状态名
}

//具体状态类:新建状态
class New extends ThreadState {
	public New() {
		stateName = "新建状态";
		System.out.println("当前线程处于:新建状态.");
	}

	public void start(ThreadContext hj) {
		System.out.print("调用start()方法-->");
		if (stateName.equals("新建状态")) {
			hj.setState(new Runnable());
		} else {
			System.out.println("当前线程不是新建状态,不能调用start()方法.");
		}
	}
}

//具体状态类:就绪状态
class Runnable extends ThreadState {
	public Runnable() {
		stateName = "就绪状态";
		System.out.println("当前线程处于:就绪状态.");
	}

	public void getCPU(ThreadContext hj) {
		System.out.print("获得CPU时间-->");
		if (stateName.equals("就绪状态")) {
			hj.setState(new Running());
		} else {
			System.out.println("当前线程不是就绪状态,不能获取CPU.");
		}
	}
}

//具体状态类:运行状态
class Running extends ThreadState {
	public Running() {
		stateName = "运行状态";
		System.out.println("当前线程处于:运行状态.");
	}

	public void suspend(ThreadContext hj) {
		System.out.print("调用suspend()方法-->");
		if (stateName.equals("运行状态")) {
			hj.setState(new Blocked());
		} else {
			System.out.println("当前线程不是运行状态,不能调用suspend()方法.");
		}
	}

	public void stop(ThreadContext hj) {
		System.out.print("调用stop()方法-->");
		if (stateName.equals("运行状态")) {
			hj.setState(new Dead());
		} else {
			System.out.println("当前线程不是运行状态,不能调用stop()方法.");
		}
	}
}

//具体状态类:阻塞状态
class Blocked extends ThreadState {
	public Blocked() {
		stateName = "阻塞状态";
		System.out.println("当前线程处于:阻塞状态.");
	}

	public void resume(ThreadContext hj) {
		System.out.print("调用resume()方法-->");
		if (stateName.equals("阻塞状态")) {
			hj.setState(new Runnable());
		} else {
			System.out.println("当前线程不是阻塞状态,不能调用resume()方法.");
		}
	}
}

//具体状态类:死亡状态
class Dead extends ThreadState {
	public Dead() {
		stateName = "死亡状态";
		System.out.println("当前线程处于:死亡状态.");
	}
}

 状态模式的应用场景
  1、当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为时,就可以考虑使用状态模式
  2、一个操作中含有庞大的分支结构,并且这些分支决定于对象的状态时
 状态模式的扩展
  在有些情况下,可能有多个环境对象需要共享一组状态,这时需要引入享元模式,将这些具体状态对象放在集合中供程序共享,即共享状态模式。
在这里插入图片描述

public class FlyweightStatePattern {
	public static void main(String[] args) {
		ShareContext context = new ShareContext(); // 创建环境
		context.Handle(); // 处理请求
		context.Handle();
		context.Handle();
		context.Handle();
	}
}

//环境类
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);
	}
}

//抽象状态类
abstract class ShareState {
	public abstract void Handle(ShareContext context);
}

//具体状态1类
class ConcreteState1 extends ShareState {
	public void Handle(ShareContext context) {
		System.out.println("当前状态是: 状态1");
		context.setState(context.getState("2"));
	}
}

//具体状态2类
class ConcreteState2 extends ShareState {
	public void Handle(ShareContext context) {
		System.out.println("当前状态是: 状态2");
		context.setState(context.getState("1"));
	}
}

 共享状态模式的不同之处是在环境类中增加了一个 HashMap 来保存相关状态,当需要某种状态时可以从中获取。
 状态模式与责任链模式的区别
  状态模式和责任链模式都能消除 if-else 分支过多的问题。但在某些情况下,状态模式中的状态可以理解为责任,那么在这种情况下,两种模式都可以使用。从定义来看,状态模式强调的是一个对象内在状态的改变,而责任链模式强调的是外部节点对象间的改变。从代码实现上来看,两者最大的区别就是状态模式的各个状态对象知道自己要进入的下一个状态对象,而责任链模式并不清楚其下一个节点处理对象,因为链式组装由客户端负责。
 状态模式与策略模式的区别
  状态模式和策略模式的 UML 类图架构几乎完全一样,但两者的应用场景是不一样的。策略模式的多种算法行为择其一都能满足,彼此之间是独立的,用户可自行更换策略算法,而状态模式的各个状态间存在相互关系,彼此之间在一定条件下存在自动切换状态的效果,并且用户无法指定状态,只能设置初始状态。

六、观察者模式
 观察者(Observer)模式的定义:指多个对象间存在一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式、模型-视图模式,它是对象行为型模式。
 观察者模式是一种对象行为型模式,其主要优点如下:
  1、降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则
  2、目标与观察者之间建立了一套触发机制

 它的主要缺点如下:
  1、目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用
  2、当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率
 观察者模式的结构
 实现观察者模式时要注意具体目标对象和具体观察者对象之间不能直接调用,否则将使两者之间紧密耦合起来,这违反了面向对象的设计原则。
 观察者模式的主要角色
  1、抽象主题(Subject)角色:也叫抽象目标类,它提供了一个用于保存观察者对象的聚集类和增加、删除观察者对象的方法,以及通知所有观察者的抽象方法
  2、具体主题(Concrete Subject)角色:也叫具体目标类,它实现抽象目标中的通知方法,当具体主题的内部状态发生改变时,通知所有注册过的观察者对象
  3、抽象观察者(Observer)角色:它是一个抽象类或接口,它包含了一个更新自己的抽象方法,当接到具体主题的更改通知时被调用
  4、具体观察者(Concrete Observer)角色:实现抽象观察者中定义的抽象方法,以便在得到目标的更改通知时更新自身的状态
在这里插入图片描述

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();
    }
}
//抽象目标
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(); //通知观察者方法
}
//具体目标
class ConcreteSubject extends Subject {
    public void notifyObserver() {
        System.out.println("具体目标发生改变...");
        System.out.println("--------------");
        for (Object obs : observers) {
            ((Observer) obs).response();
        }
    }
}
//抽象观察者
interface Observer {
    void response(); //反应
}
//具体观察者1
class ConcreteObserver1 implements Observer {
    public void response() {
        System.out.println("具体观察者1作出反应!");
    }
}
//具体观察者1
class ConcreteObserver2 implements Observer {
    public void response() {
        System.out.println("具体观察者2作出反应!");
    }
}

 观察者模式在软件幵发中用得最多的是窗体程序设计中的事件处理,窗体中的所有组件都是“事件源”,也就是目标对象,而事件处理程序类的对象是具体观察者对象。
 观察者模式的应用场景
  在软件系统中,当系统一方行为依赖另一方行为的变动时,可使用观察者模式松耦合联动双方,使得一方的变动可以通知到感兴趣的另一方对象,从而让另一方对象对此做出响应。
  1、对象间存在一对多关系,一个对象的状态发生改变会影响其他对象
  2、当一个抽象模型有两个方面,其中一个方面依赖于另一方面时,可将这二者封装在独立的对象中以使它们可以各自独立地改变和复用
  3、实现类似广播机制的功能,不需要知道具体收听者,只需分发广播,系统中感兴趣的对象会自动接收该广播
  4、多层级嵌套使用,形成一种链式触发机制,使得事件具备跨域(跨越两种观察者类型)通知
 观察者模式的扩展
  在 Java 中,通过 java.util.Observable 类和 java.util.Observer 接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式实例。
  Observable 类是抽象目标类,它有一个 Vector 向量,用于保存所有要通知的观察者对象,下面来介绍它最重要的 3 个方法:
  ①void addObserver(Observer o) 方法:用于将新的观察者对象添加到向量中
  ②void notifyObservers(Object arg) 方法:调用向量中的所有观察者对象的 update() 方法,通知它们数据发生改变。通常越晚加入向量的观察者越先得到通知
  ③void setChange() 方法:用来设置一个 boolean 类型的内部标志位,注明目标对象发生了变化。当它为真时,notifyObservers() 才会通知观察者
  Observer 接口是抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用 void update(Observable o,Object arg) 方法,进行相应的工作。
 示例:利用 Observable 类和 Observer 接口实现原油期货的观察者模式
在这里插入图片描述

public class CrudeOilFutures {
    public static void main(String[] args) {
        OilFutures oil = new OilFutures();
        Observer bull = new Bull(); //多方
        Observer bear = new Bear(); //空方
        oil.addObserver(bull);
        oil.addObserver(bear);
        oil.setPrice(10);
        oil.setPrice(-8);
    }
}
//具体目标类:原油期货
class OilFutures extends Observable {
    private float price;
    public float getPrice() {
        return this.price;
    }
    public void setPrice(float price) {
        super.setChanged();  //设置内部标志位,注明数据发生变化
        super.notifyObservers(price);    //通知观察者价格改变了
        this.price = price;
    }
}
//具体观察者类:多方
class Bull implements Observer {
    public void update(Observable o, Object arg) {
        Float price = ((Float) arg).floatValue();
        if (price > 0) {
            System.out.println("油价上涨" + price + "元,多方高兴了!");
        } else {
            System.out.println("油价下跌" + (-price) + "元,多方伤心了!");
        }
    }
}
//具体观察者类:空方
class Bear implements Observer {
    public void update(Observable o, Object arg) {
        Float price = ((Float) arg).floatValue();
        if (price > 0) {
            System.out.println("油价上涨" + price + "元,空方伤心了!");
        } else {
            System.out.println("油价下跌" + (-price) + "元,空方高兴了!");
        }
    }
}

七、中介者模式
 在现实生活中,常常会出现好多对象之间存在复杂的交互关系,这种交互关系常常是“网状结构”,它要求每个对象都必须知道它需要交互的对象。例如,每个人必须记住他(她)所有朋友的电话;而且,朋友中如果有人的电话修改了,他(她)必须让其他所有的朋友一起修改,这叫作“牵一发而动全身”,非常复杂。
 如果把这种“网状结构”改为“星形结构”的话,将大大降低它们之间的“耦合性”,这时只要找一个“中介者”就可以了。如前面所说的“每个人必须记住所有朋友电话”的问题,只要在网上建立一个每个朋友都可以访问的“通信录”就解决了。这样的例子还有很多,例如,你刚刚参加工作想租房,可以找“房屋中介”;或者,自己刚刚到一个陌生城市找工作,可以找“人才交流中心”帮忙。
 在软件的开发过程中,这样的例子也很多,例如,在 MVC 框架中,控制器(C)就是模型(M)和视图(V)的中介者;还有大家常用的 QQ 聊天程序的“中介者”是 QQ 服务器。所有这些,都可以采用“中介者模式”来实现,它将大大降低对象之间的耦合性,提高系统的灵活性。
 中介者(Mediator)模式的定义:定义一个中介对象来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间的交互。中介者模式又叫调停模式,它是迪米特法则的典型应用。
 中介者模式是一种对象行为型模式,其主要优点如下:
  1、类之间各司其职,符合迪米特法则
  2、降低了对象之间的耦合性,使得对象易于独立地被复用
  3、将对象间的一对多关联转变为一对一的关联,提高系统的灵活性,使得系统易于维护和扩展
 其主要缺点是:中介者模式将原本多个对象直接的相互依赖变成了中介者和多个同事类的依赖关系。当同事类越多时,中介者就会越臃肿,变得复杂且难以维护。
 中介者模式的结构
 中介者模式包含以下主要角色:
  1、抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法
  2、具体中介者(Concrete Mediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色
  3、抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
  4、具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互
在这里插入图片描述

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();
    }
}
//抽象中介者
abstract class Mediator {
    public abstract void register(Colleague colleague);
    public abstract void relay(Colleague cl); //转发
}
//具体中介者
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();
            }
        }
    }
}
//抽象同事类
abstract class Colleague {
    protected Mediator mediator;
    public void setMedium(Mediator mediator) {
        this.mediator = mediator;
    }
    public abstract void receive();
    public abstract void send();
}
//具体同事类1
class ConcreteColleague1 extends Colleague {
    public void receive() {
        System.out.println("具体同事类1收到请求。");
    }
    public void send() {
        System.out.println("具体同事类1发出请求。");
        mediator.relay(this); //请中介者转发
    }
}
//具体同事类2
class ConcreteColleague2 extends Colleague {
    public void receive() {
        System.out.println("具体同事类2收到请求。");
    }
    public void send() {
        System.out.println("具体同事类2发出请求。");
        mediator.relay(this); //请中介者转发
    }
}

 在实际开发中,通常采用以下两种方法来简化中介者模式,使开发变得更简单:
  ①不定义中介者接口,把具体中介者对象实现成为单例
  ②同事对象不持有中介者,而是在需要的时候直接获取中介者对象并调用
在这里插入图片描述

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();
    }
}
//简单单例中介者
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();
            }
        }
    }
}
//抽象同事类
interface SimpleColleague {
    void receive();
    void send();
}
//具体同事类1
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); //请中介者转发
    }
}
//具体同事类2
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); //请中介者转发
    }
}

八、迭代器模式
 迭代器(Iterator)模式的定义:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式,其主要优点如下:
  1、访问一个聚合对象的内容而无须暴露它的内部表示
  2、遍历任务交由迭代器完成,这简化了聚合类
  3、它支持以不同方式遍历一个聚合,甚至可以自定义迭代器的子类以支持新的遍历
  4、增加新的聚合类和迭代器类都很方便,无须修改原有代码
  5、封装性良好,为遍历不同的聚合结构提供一个统一的接口
 其主要缺点是:增加了类的个数,这在一定程度上增加了系统的复杂性。
 在日常开发中,我们几乎不会自己写迭代器。除非需要定制一个自己实现的数据结构对应的迭代器,否则,开源框架提供的 API 完全够用。
 迭代器模式的结构
 迭代器模式是通过将聚合对象的遍历行为分离出来,抽象成迭代器类来实现的,其目的是在不暴露聚合对象的内部结构的情况下,让外部代码透明地访问聚合的内部数据。
 迭代器模式主要包含以下角色:
  1、抽象聚合(Aggregate)角色:定义存储、添加、删除聚合对象以及创建迭代器对象的接口
  2、具体聚合(ConcreteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例
  3、抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含 hasNext()、first()、next() 等方法
  4、具体迭代器(Concretelterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
在这里插入图片描述

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());
    }
}
//抽象聚合
interface Aggregate {
    public void add(Object obj);
    public void remove(Object obj);
    public Iterator getIterator();
}
//具体聚合
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));
    }
}
//抽象迭代器
interface Iterator {
    Object first();
    Object next();
    boolean hasNext();
}
//具体迭代器
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;
    }
}

 迭代器模式的应用场景
  1、当需要为聚合对象提供多种遍历方式时
  2、当需要为遍历不同的聚合结构提供一个统一的接口时
  3、当访问一个聚合对象的内容而无须暴露其内部细节的表示时
 由于集合与迭代器的关系非常密切,所以大多数语言在实现聚合类时都提供了迭代器类
 迭代器模式的扩展
  迭代器模式常常与组合模式结合起来使用,在对组合模式中的容器构件进行访问时,经常将迭代器潜藏在组合模式的容器构成类中。当然,也可以构造一个外部迭代器来对容器构件进行访问。
在这里插入图片描述
九、访问者模式
 在现实生活中,有些集合对象存在多种不同的元素,且每种元素也存在多种不同的访问者和处理方式。这些被处理的数据元素相对稳定而访问方式多种多样的数据结构,如果用“访问者模式”来处理比较方便。访问者模式能把处理方法从数据结构中分离出来,并可以根据需要增加新的处理方法,且不用修改原来的程序代码与数据结构,这提高了程序的扩展性和灵活性。
 访问者(Visitor)模式的定义:将作用于某种数据结构中的各元素的操作分离出来封装成独立的类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离,是行为类模式中最复杂的一种模式。
 访问者(Visitor)模式是一种对象行为型模式,其主要优点如下:
  1、扩展性好:能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
  2、复用性好:可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度
  3、灵活性好:访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构
  4、符合单一职责原则:访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一
 访问者(Visitor)模式的主要缺点如下:
  1、增加新的元素类很困难:在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”
  2、破坏封装:访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性
  3、违反了依赖倒置原则:访问者模式依赖了具体类,而没有依赖抽象类
 访问者模式的结构
  访问者(Visitor)模式实现的关键是如何将作用于元素的操作分离出来封装成独立的类。
 访问者模式包含以下主要角色:
  1、抽象访问者(Visitor)角色:定义一个访问具体元素的接口,为每个具体元素类对应一个访问操作 visit() ,该操作中的参数类型标识了被访问的具体元素
  2、具体访问者(ConcreteVisitor)角色:实现抽象访问者角色中声明的各个访问操作,确定访问者访问一个元素时该做什么
  3、抽象元素(Element)角色:声明一个包含接受操作 accept() 的接口,被接受的访问者对象作为 accept() 方法的参数
  4、具体元素(ConcreteElement)角色:实现抽象元素角色提供的 accept() 操作,其方法体通常都是 visitor.visit(this) ,另外具体元素中可能还包含本身业务逻辑的相关操作
  5、对象结构(Object Structure)角色:是一个包含元素角色的容器,提供让访问者对象遍历容器中的所有元素的方法,通常由 List、Set、Map 等聚合类实现
在这里插入图片描述

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);
    }
}
//抽象访问者
interface Visitor {
    void visit(ConcreteElementA element);
    void visit(ConcreteElementB element);
}
//具体访问者A类
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类
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());
    }
}
//抽象元素类
interface Element {
    void accept(Visitor visitor);
}
//具体元素A类
class ConcreteElementA implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public String operationA() {
        return "具体元素A的操作。";
    }
}
//具体元素B类
class ConcreteElementB implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
    public String operationB() {
        return "具体元素B的操作。";
    }
}
//对象结构角色
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);
    }
}

 访问者模式的应用场景
  当系统中存在类型数量稳定(固定)的一类数据结构时,可以使用访问者模式方便地实现对该类型所有数据结构的不同操作,而又不会对数据产生任何副作用(脏数据)。简而言之,就是当对集合中的不同类型数据(类型数量稳定)进行多种操作时,使用访问者模式。通常在以下情况可以考虑使用访问者(Visitor)模式:
  1、对象结构相对稳定,但其操作算法经常变化的程序
  2、对象结构中的对象需要提供多种不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构
  3、对象结构包含很多类型的对象,希望对这些对象实施一些依赖于其具体类型的操作
 访问者模式的扩展
  (1)与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。
  (2)访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。
在这里插入图片描述
十、备忘录模式
 备忘录模式能记录一个对象的内部状态,当用户后悔时能撤销当前操作,使数据恢复到它原先的状态。其实很多应用软件都提供了这项功能,如 Word、记事本、Photoshop、Eclipse 等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 IE 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
 备忘录(Memento)模式的定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后当需要时能将该对象恢复到原先保存的状态。该模式又叫快照模式。
 备忘录模式是一种对象行为型模式,其主要优点如下:
  1、提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态
  2、实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息
  3、简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则
 其主要缺点是:资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
 备忘录模式的结构
  备忘录模式的核心是设计备忘录类以及用于管理备忘录的管理者类。
  备忘录模式的主要角色如下:
  1、发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
  2、备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
  3、管理者(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());
    }
}
//备忘录
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;
    }
}
//发起人
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());
    }
}
//管理者
class Caretaker {
    private Memento memento;
    public void setMemento(Memento m) {
        memento = m;
    }
    public Memento getMemento() {
        return memento;
    }
}

Tip:上面的示例只能恢复到上一个状态,即只能记录一个历史状态。
 多个历史状态的备份示例:
在这里插入图片描述

public class DatingGame {
    public static void main(String[] args) {
        new DatingGameWin();
    }
}
//客户窗体类
class DatingGameWin extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;
    JPanel CenterJP, EastJP;
    JRadioButton girl1, girl2, girl3, girl4;
    JButton button1, button2;
    String FileName;
    JLabel g;
    You you;
    GirlStack girls;
    DatingGameWin() {
        super("利用备忘录模式设计相亲游戏");
        you = new You();
        girls = new GirlStack();
        this.setBounds(0, 0, 900, 380);
        this.setResizable(false);
        FileName = "src/memento/Photo/四大美女.jpg";
        g = new JLabel(new ImageIcon(FileName), JLabel.CENTER);
        CenterJP = new JPanel();
        CenterJP.setLayout(new GridLayout(1, 4));
        CenterJP.setBorder(BorderFactory.createTitledBorder("四大美女如下:"));
        CenterJP.add(g);
        this.add("Center", CenterJP);
        EastJP = new JPanel();
        EastJP.setLayout(new GridLayout(1, 1));
        EastJP.setBorder(BorderFactory.createTitledBorder("您选择的爱人是:"));
        this.add("East", EastJP);
        JPanel SouthJP = new JPanel();
        JLabel info = new JLabel("四大美女有“沉鱼落雁之容、闭月羞花之貌”,您选择谁?");
        girl1 = new JRadioButton("西施", true);
        girl2 = new JRadioButton("貂蝉");
        girl3 = new JRadioButton("王昭君");
        girl4 = new JRadioButton("杨玉环");
        button1 = new JButton("确定");
        button2 = new JButton("返回");
        ButtonGroup group = new ButtonGroup();
        group.add(girl1);
        group.add(girl2);
        group.add(girl3);
        group.add(girl4);
        SouthJP.add(info);
        SouthJP.add(girl1);
        SouthJP.add(girl2);
        SouthJP.add(girl3);
        SouthJP.add(girl4);
        SouthJP.add(button1);
        SouthJP.add(button2);
        button1.addActionListener(this);
        button2.addActionListener(this);
        this.add("South", SouthJP);
        showPicture("空白");
        you.setWife("空白");
        girls.push(you.createMemento());    //保存状态
    }
    //显示图片
    void showPicture(String name) {
        EastJP.removeAll(); //清除面板内容
        EastJP.repaint(); //刷新屏幕
        you.setWife(name);
        FileName = "src/com/boom/pattern/memento/Photo/" + name + ".jpg";
        g = new JLabel(new ImageIcon(FileName), JLabel.CENTER);
        EastJP.add(g);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    @Override
    public void actionPerformed(ActionEvent e) {
        boolean ok = false;
        if (e.getSource() == button1) {
            ok = girls.push(you.createMemento());    //保存状态
            if (ok && girl1.isSelected()) {
                showPicture("西施");
            } else if (ok && girl2.isSelected()) {
                showPicture("貂蝉");
            } else if (ok && girl3.isSelected()) {
                showPicture("王昭君");
            } else if (ok && girl4.isSelected()) {
                showPicture("杨玉环");
            }
        } else if (e.getSource() == button2) {
            you.restoreMemento(girls.pop()); //恢复状态
            showPicture(you.getWife());
        }
    }
}
//备忘录:美女
class Girl {
    private String name;
    public Girl(String name) {
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}
//发起人:您
class You {
    private String wifeName;    //妻子
    public void setWife(String name) {
        wifeName = name;
    }
    public String getWife() {
        return wifeName;
    }
    public Girl createMemento() {
        return new Girl(wifeName);
    }
    public void restoreMemento(Girl p) {
        setWife(p.getName());
    }
}
//管理者:美女栈
class GirlStack {
    private Girl girl[];
    private int top;
    GirlStack() {
        girl = new Girl[5];
        top = -1;
    }
    public boolean push(Girl p) {
        if (top >= 4) {
            System.out.println("你太花心了,变来变去的!");
            return false;
        } else {
            girl[++top] = p;
            return true;
        }
    }
    public Girl pop() {
        if (top <= 0) {
            System.out.println("美女栈空了!");
            return girl[0];
        } else return girl[top--];
    }
}

 应用场景
  1、需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能
  2、需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作
 备忘录模式的扩展
  在备忘录模式中,通过定义“备忘录”来备份“发起人”的信息,而原型模式的 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());
    }
}
//发起人原型
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;
    }
}
//原型管理者
class PrototypeCaretaker {
    private OriginatorPrototype opt;
    public void setMemento(OriginatorPrototype opt) {
        this.opt = opt;
    }
    public OriginatorPrototype getMemento() {
        return opt;
    }
}

十一、解释器模式
 解释器(Interpreter)模式的定义:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。也就是说,用编译语言的方式来分析应用中的实例。这种模式实现了文法表达式处理的接口,该接口解释一个特定的上下文。这里提到的文法和句子的概念同编译原理中的描述相同,“文法”指语言的语法规则,而“句子”是语言集中的元素。
 解释器模式是一种类行为型模式,其主要优点如下:
  1、扩展性好:由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法
  2、容易实现:在语法树中的每个表达式节点类都是相似的,所以实现其文法较为容易
 解释器模式的主要缺点如下:
  1、执行效率较低:解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度很慢,且代码的调试过程也比较麻烦
  2、会引起类膨胀:解释器模式中的每条规则至少需要定义一个类,当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护
  3、可应用的场景比较少:在软件开发中,需要定义语言文法的应用实例非常少,所以这种模式很少被使用到
 解释器模式的结构
  解释器模式常用于对简单语言的编译或分析实例中,为了掌握好它的结构与实现,必须先了解编译原理中的“文法、句子、语法树”等相关概念。
  文法是用于描述语言的语法结构的形式规则。没有规矩不成方圆,例如,有些人认为完美爱情的准则是“相互吸引、感情专一、任何一方都没有恋爱经历”,虽然最后一条准则较苛刻,但任何事情都要有规则,语言也一样,不管它是机器语言还是自然语言,都有它自己的文法规则。
  句子是语言的基本单位,是语言集中的一个元素,它由终结符构成,能由“文法”推导出。
  语法树是句子结构的一种树型表示,它代表了句子的推导结果,它有利于理解句子语法结构的层次。
在这里插入图片描述
 解释器模式包含以下主要角色:
  1、抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()
  2、终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应
  3、非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式
  4、环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
  5、客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法
在这里插入图片描述
 解释器模式实现的关键是定义文法规则、设计终结符类与非终结符类、画出结构图,必要时构建语法树。

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

 示例:假如“韶粵通”公交车读卡器可以判断乘客的身份,如果是“韶关”或者“广州”的“老人” “妇女”“儿童”就可以免费乘车,其他人员乘车一次扣 2 元。

/*文法规则
  <expression> ::= <city>的<person>
  <city> ::= 韶关|广州
  <person> ::= 老人|妇女|儿童
*/
public class InterpreterPatternDemo {
    public static void main(String[] args) {
        Context bus = new Context();
        bus.freeRide("韶关的老人");
        bus.freeRide("韶关的年轻人");
        bus.freeRide("广州的妇女");
        bus.freeRide("广州的儿童");
        bus.freeRide("山东的儿童");
    }
}
//抽象表达式类
interface Expression {
    public boolean interpret(String info);
}
//终结符表达式类
class TerminalExpression implements Expression {
    private Set<String> set = new HashSet<String>();
    public TerminalExpression(String[] data) {
        for (int i = 0; i < data.length; i++) set.add(data[i]);
    }
    public boolean interpret(String info) {
        if (set.contains(info)) {
            return true;
        }
        return false;
    }
}
//非终结符表达式类
class AndExpression implements Expression {
    private Expression city = null;
    private Expression person = null;
    public AndExpression(Expression city, Expression person) {
        this.city = city;
        this.person = person;
    }
    public boolean interpret(String info) {
        String s[] = info.split("的");
        return city.interpret(s[0]) && person.interpret(s[1]);
    }
}
//环境类
class Context {
    private String[] citys = {"韶关", "广州"};
    private String[] persons = {"老人", "妇女", "儿童"};
    private Expression cityPerson;
    public Context() {
        Expression city = new TerminalExpression(citys);
        Expression person = new TerminalExpression(persons);
        cityPerson = new AndExpression(city, person);
    }
    public void freeRide(String info) {
        boolean ok = cityPerson.interpret(info);
        if (ok) System.out.println("您是" + info + ",您本次乘车免费!");
        else System.out.println(info + ",您不是免费人员,本次乘车扣费2元!");
    }
}

 应用场景
  1、当语言的文法较为简单,且执行效率不是关键问题时
  2、当问题重复出现,且可以用一种简单的语言来进行表达时
  3、当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释
 解释器模式的扩展
  在项目开发中,如果要对数据表达式进行分析与计算,无须再用解释器模式进行设计了,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时间=利息,其相关代码如下:

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);
    }
}
  • 5
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值