行为型模式
行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象无法完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分派行为。由于组合关系或聚合关系比继承关系耦合度低,满足"合成复用原则",所以对象行为模式比类行为模式更具有灵活性。
行为型模式分为:
- 模板方法模式
- 策略模式
- 命令模式
- 职责链模式
- 观察者模式
- 中介者模式
- 迭代器模式
- 访问者模式
- 备忘录模式
- 解释器模式
以上11种行为型模式,除了模板方法模式和解释器模式是类行为模式,其他都是对象行为型模式。
模板方法模式
概述
在面向对象程序设计过程中,程序员常常遇到这种情况: 设计一个系统时知道了算法所需的关键步骤,而且确定了这些步骤的执行顺序,但某些步骤的具体实现还未知,或者说某些步骤的实现与具体的环境相关。
例如,去银行办理业务一般要经历以下4个流程:取号,排队,办理具体业务、对银行工作人员进行评分等,其中取号、排队和对银行工作人员评分的业务对每个客户都是一样的。可以在父类中实现,但是办理具体业务却因人而异,他可能是存款,取款,转账等。可以延迟到子类中实现。
定义
定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重新定义该算法的某些特定步骤。
结构
模板方法(Template Method)模式包含以下主要角色:
- 抽象类:负责给出一个算法的轮廓和骨架,它由一个模板方法和若干个基本方法构成。
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分3种:
- 抽象方法:一个抽象方法由抽象类声明,由具体子类实现
- 具体方法:一个具体方法由抽象类或具体类声明实现,其子类也可以进行覆盖也可以直接继承
- 钩子方法:在抽象类种已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。一般用于判断的逻辑方法
boolean isXxx()
- 具体子类:实现抽象类中所定义的抽象方法和钩子方法,它们是一个顶级逻辑的组成步骤。
案例实现
【例】制作豆浆
豆浆的制作步骤是固定的,分别为选材,选配料,浸泡,打磨等。类图如下:
代码展示:
// 豆浆-抽象类角色
public abstract class SoyaMilk {
//模板方法,make , 通常定义为final,禁止子类重写
final void make() {
select();
if (isAddCondiments()) {
addCondiments();
}
soak();
beat();
}
// 选材料
void select() {
System.out.println("选择好的新鲜黄豆");
}
// 添加不同的配料,抽象方法,子类具体实现
abstract void addCondiments();
// 浸泡
void soak() {
System.out.println("黄豆和配料开始浸泡,需要3小时");
}
// 打碎
void beat() {
System.out.println("将黄豆和配料放入豆浆机中,开始打碎");
}
// 是否加配料 -- 钩子方法
boolean isAddCondiments() {
return true;
}
}
// 花生配料- 具体子类角色
public class PeanutSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("添加配料: 花生");
}
}
// 红豆配料- 具体子类角色
public class RedBeanSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
System.out.println("添加配料: 红豆");
}
}
// 纯豆浆-不加配料 - 具体子类角色
public class PureSoyaMilk extends SoyaMilk {
@Override
void addCondiments() {
}
@Override
boolean isAddCondiments() {
return false;
}
}
测试:
public class Client {
public static void main(String[] args) {
System.out.println("------花生豆浆的制作--------");
PeanutSoyaMilk peanutSoyaMilk = new PeanutSoyaMilk();
peanutSoyaMilk.make();
System.out.println("------红豆豆浆的制作--------");
RedBeanSoyaMilk redBeanSoyaMilk = new RedBeanSoyaMilk();
redBeanSoyaMilk.make();
System.out.println("------纯豆浆的制作--------");
PureSoyaMilk pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.make();
}
}
优缺点
优点:
- 提高代码复用性 将相同部分的代码放在抽象的父类中,而将不同的代码放入不同的子类中
- 实现了反转控制 通过一个父类调用其子类的操作,通过对子类的具体实现扩展为不同的行为,实现了反向控制,并符合“开闭原则”
缺点: - 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象。
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果。这导致一种反向控制结构,提高了代码阅读的难度
使用场景
- 算法的整体步骤很固定,但其中个别部分易变时,这时候可以使用模板方法模式,将容易变的部分抽象出来,供子类实现。
- 需要通过子类来决定父类算法中某个步骤是否执行,实现子类对父类的反向控制。
在源码中的使用
Spring IOC
ConfigurableApplicationContext接口的refresh()是模板方法,类图如下:
代码:
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
/**
* @Author: mei_ming
* @DateTime: 2022/12/13 21:46
* @Description: Spring IOC 中的模板方法
*/
public class TemplateTest {
public static void main(String[] args) {
// ConfigurableApplicationContext
// AbstractApplicationContext
}
/**
* 1.
* public interface ConfigurableApplicationContext{
* // 声明了一个模板方法
* void refresh() throws BeansException, IllegalStateException;
* }
*
* 2.
* public abstract class AbstractApplicationContext extends DefaultResourceLoader
* implements ConfigurableApplicationContext......{
* // 实现了refresh()模板方法
* public void refresh() throws BeansException, IllegalStateException {
* synchronized(this.startupShutdownMonitor) {
* this.prepareRefresh();
* // obtainFreshBeanFactory() 中 有 抽象方法
* ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
* this.prepareBeanFactory(beanFactory);
*
* try {
* // postProcessBeanFactory() 模板方法模式中的钩子方法
* this.postProcessBeanFactory(beanFactory);
* this.invokeBeanFactoryPostProcessors(beanFactory);
* this.registerBeanPostProcessors(beanFactory);
* this.initMessageSource();
* this.initApplicationEventMulticaster();
* // onRefresh() 模板方法模式中的钩子方法
* this.onRefresh();
* this.registerListeners();
* this.finishBeanFactoryInitialization(beanFactory);
* this.finishRefresh();
* } catch (BeansException var5) {
* this.destroyBeans();
* this.cancelRefresh(var5);
* throw var5;
* }
* }
* }
* protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
* // refreshBeanFactory() 模板方法模式中的抽象方法,由子类具体实现
* this.refreshBeanFactory();
* // getBeanFactory() 模板方法模式中的抽象方法,由子类具体实现
* ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
* if (this.logger.isDebugEnabled()) {
* this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
* }
*
* return beanFactory;
* }
* // 模板方法模式中的抽象方法,由子类具体实现
* protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
* public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
*
* // 模板方法模式中的钩子方法
* protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
* }
* protected void onRefresh() throws BeansException {
* }
* }
*/
}
InputStream的read()
read(byte b[], int off, int len)
: 相当于 模板模式中的 模板方法public abstract int read() throws IOException;
: 模板模式中的抽象方法,要求子类重写
import java.io.IOException;
import java.io.InputStream;
/**
* @Author: mei_ming
* @DateTime: 2022/12/13 22:21
* @Description: InputStream类中的read()
*/
public class TemplateTest {
public static void main(String[] args) {
// InputStream
}
/**
* 1.
* public abstract class InputStream implements Closeable {
* // 模板模式中的抽象方法,要求子类重写
* public abstract int read() throws IOException;
*
* public int read(byte b[]) throws IOException {
* return read(b, 0, b.length);
* }
*
* // read(byte b[], int off, int len) 相当于 模板模式中的 模板方法
* public int read(byte b[], int off, int len) throws IOException {
* if (b == null) {
* throw new NullPointerException();
* } else if (off < 0 || len < 0 || len > b.length - off) {
* throw new IndexOutOfBoundsException();
* } else if (len == 0) {
* return 0;
* }
*
* int c = read(); //调用了无参的read方法,该方法是每次读取一个字节数据
* if (c == -1) {
* return -1;
* }
* b[off] = (byte)c;
*
* int i = 1;
* try {
* for (; i < len ; i++) {
* c = read();
* if (c == -1) {
* break;
* }
* b[off + i] = (byte)c;
* }
* } catch (IOException ee) {
* }
* return i;
* }
* }
* 2.
* public class FileInputStream extends InputStream {
* // FileInputStream子类实现read()方法
* public int read() throws IOException {
* return read0();
* }
*
* private native int read0() throws IOException;
* }
*/
}
策略模式
概述
作为一名程序猿,开发需要选择一款开发工具,当然可以进行代码开发的工具有很多,可以选择idea进行开发,也可以使用eclipse进行开发。
定义:
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式。它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
结构
策略模式的主要角色如下:
- 抽象策略(Straegy)类:这是一个抽象角色,通常由一个接口或抽象类实现,此角色给出所有的具体策略类所需的接口。
- 具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。
- 环境(Context)类:持有一个或多个策略类的引用,最终给客户端调用
案例实现
【例】促销活动
一家百货公司在定年度的促销活动。针对不同的节日(春节、中秋、圣诞)推出不同的促销活动,由促销员将促销活动展示给客户。类图如下:
代码如下:
// 抽象策略类
public interface Strategy {
void show();
}
// 具体策略类
public class StrategyA implements Strategy{
@Override
public void show() {
System.out.println("StrategyA 买一送一");
}
}
public class StrategyB implements Strategy{
@Override
public void show() {
System.out.println("StrategyB 充200返20");
}
}
public class StrategyC implements Strategy{
@Override
public void show() {
System.out.println("StrategyC 充会员领礼品");
}
}
// 环境类
public class SalesMan {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
// 由销售员展示促销活动给客户
public void salesManShow(){
strategy.show();
}
}
测试:
public class StrategyTest {
public static void main(String[] args) {
//春节到了,展示 StrategyA 买一送一
System.out.println("---------春节--------");
SalesMan salesMan = new SalesMan();
salesMan.setStrategy(new StrategyA());
salesMan.salesManShow();
//中秋到了,展示 StrategyB 充200返20
System.out.println("---------中秋--------");
salesMan.setStrategy(new StrategyB());
salesMan.salesManShow();
//圣诞来临, 展示 StrategyC 充会员领礼品
System.out.println("---------圣诞--------");
salesMan.setStrategy(new StrategyC());
salesMan.salesManShow();
}
}
优缺点
优点
- 策略类之间可以自由切换。由于策略类都实现同一接口,所以使它们之间可以自由切换。
- 易于扩展。增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则”
- 避免使用多重条件选择语句(if - else),充分体现面向对象设计思想。
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量。
使用场景
- 一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句
- 系统中各算法彼此完全独立,且要求对客户隐藏具体算法的实现细节时。
- 系统要求使用算法的客户类不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
JDK源码解析
在JDK的Arrays的Comparator就使用了策略模式。具体分析如下
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
/**
* @Author: mei_ming
* @DateTime: 2022/12/14 21:46
* @Description: 策略模式在Arrays.sort()中的使用
*/
public class StrategyTest {
public static void main(String[] args) {
Integer[] data = {9,1,2,8,3,5,6,4};
// 实现升序排序,返回-1放左边,1放右边, 0保持不变
/**
* 说明1:
* 1. 实现了Comparator接口(抽象策略类),
* 2. 匿名类对象 new Comparator<Integer>(){}: 是具体策略类,对抽象策略类的实现
* 3. public int compare(Integer o1, Integer o2) {} : 指定具体的处理方式
*/
Comparator<Integer> comparator = new Comparator<Integer>(){
@Override
public int compare(Integer o1, Integer o2) {
if (o1>o2){
return 1;
}else{
return -1;
}
}
};
Arrays.sort(data,comparator);
/**
* 说明2:
* public static <T> void sort(T[] a, Comparator<? super T> c) {
* if (c == null) {
* sort(a); //默认实现
* } else {
* if (LegacyMergeSort.userRequested) // 使用策略对象c
* legacyMergeSort(a, c);
* else
* // 使用策略对象c
* TimSort.sort(a, 0, a.length, c, null, 0, 0);
* }
* }
*/
System.out.println(Arrays.toString(data)); // 升序:[1, 2, 3, 4, 5, 6, 8, 9]
System.out.println("----------------------------");
// lambda 表达式简化
Integer[] data2 = {19,11,12,18,13,15,16,14};
Arrays.sort(data2,(var1,var2)->{
if (var1.compareTo(var2)>0){
return -1;
}else{
return 1;
}
});
System.out.println(Arrays.toString(data2)); // 降序:[19, 18, 16, 15, 14, 13, 12, 11]
}
}
命令模式
概述
【问】
现有一套智能家电,有电灯,空调,电视机等,每个家电都有自己的控制器,现需要对它们进行统一的开关控制。
可以考虑使用命令模式,动作的请求者是遥控器,动作的执行者是家电。
命令模式可将“请求者”和“执行者”分隔开来。
基本介绍:
- 命令模式(Command):在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需要在程序运行时指定具体的请求接收者即可,这时可以使用命令模式来设计。
- 命令模式使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦。
- 在命令模式中,会将一个请求封装为一个对象,以便使用不同参数来表示不同的请求(即命令),同时命令模式也支持可撤销的操作。
- 将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开,这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加、管理。
结构
命令模式包括以下主要角色:
- 抽象命令(Command)角色:定义命令的接口,声明执行的方法
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令的执行操作。
- 实现者/接收者(Receiver)角色:接收者,真正执行命令的对象,任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色:要求命令对象执行请求,通常会持有命令对象,可以持有多个命令对象。这个使客户端真正触发命令并要求命令执行相应操作的地方,也就是使用命令对象的入口。
将军发布命令,士兵去执行。请求者是将军,接收者是士兵,命令是具体命令角色。
案例实现
【例】
遥控器控制多个家电的开闭
遥控器是调用者角色,家电是接收者角色,开/关:是具体的命令;类图如下:
代码实现:
// 抽象命令角色
public interface Command {
// 执行动作
public void execute();
// 撤销动作
public void undo();
}
// 接收者角色
public class LightReceiver {
public void on(){
System.out.println(" 电灯打开了 ");
}
public void off(){
System.out.println(" 电灯关闭了 ");
}
}
// 具体命令角色-关电灯
public class LightOffCommand implements Command{
LightReceiver light;
public LightOffCommand(LightReceiver light){
this.light=light;
}
@Override
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
// 具体命令角色-开电灯
public class LightOnCommand implements Command{
LightReceiver light;
public LightOnCommand(LightReceiver light){
this.light=light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
// 空实现具体命令,用于初始化
public class NoCommand implements Command{
@Override
public void execute() {
}
@Override
public void undo() {
}
}
// 请求者角色
public class RemoteController {
// 开命令
Command[] onCommands;
// 关命令
Command[] offCommands;
// 撤销命令
Command undoCommands;
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i] = new NoCommand();
offCommands[i] = new NoCommand();
}
}
// 给按钮添加指令
public void setCommand(int no, Command onCommand, Command offCommand) {
onCommands[no] = onCommand;
offCommands[no] = offCommand;
}
// 按下开按钮
public void onBtnWasPushed(int no) {
onCommands[no].execute();
//记录撤销的命令
undoCommands = onCommands[no];
}
// 按下关按钮
public void offBtnWasPushed(int no) {
offCommands[no].execute();
//记录撤销的命令
undoCommands = offCommands[no];
}
// 按下撤销按钮
public void undoBtnWasPushed() {
undoCommands.undo();
}
}
测试:
public class Client {
public static void main(String[] args) {
// 使用命令模式,完成通过遥控器,对电灯的操作
// 创建遥控器 (初始化5组开关指令)
RemoteController controller = new RemoteController();
// 创建电灯开关命令
LightReceiver receiver = new LightReceiver();
LightOnCommand onCommand = new LightOnCommand(receiver);
LightOffCommand offCommand = new LightOffCommand(receiver);
// 将电灯开关命令,设置到遥控器上 0:代表位于第一组命令上
controller.setCommand(0,onCommand,offCommand);
// 按下 开电灯 按钮
controller.onBtnWasPushed(0);
// 按下 关电灯 按钮
controller.offBtnWasPushed(0);
// 按下 撤销 按钮
controller.undoBtnWasPushed();
}
}
想要扩展对电视的控制,只需增加命令角色和接收角色即可,不用修改其他的类
// 新增 电视接收者
public class TVReceiver {
public void on(){
System.out.println(" 电视打开了 ");
}
public void off(){
System.out.println(" 电视关闭了 ");
}
}
// 具体命令角色-开电视
public class TVOnCommand implements Command {
TVReceiver tv;
public TVOnCommand(TVReceiver tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.on();
}
@Override
public void undo() {
tv.off();
}
}
// 具体命令角色-关电视
public class TVOffCommand implements Command {
TVReceiver tv;
public TVOffCommand(TVReceiver tv) {
this.tv = tv;
}
@Override
public void execute() {
tv.off();
}
@Override
public void undo() {
tv.on();
}
}
//使用 ,在main方法中额外增加电视控制命令即可
public class Client {
public static void main(String[] args) {
// 使用命令模式,完成通过遥控器,对电灯的操作
// 创建遥控器 (初始化5组开关指令)
RemoteController controller = new RemoteController();
// 创建电灯开关命令
LightReceiver receiver = new LightReceiver();
LightOnCommand onCommand = new LightOnCommand(receiver);
LightOffCommand offCommand = new LightOffCommand(receiver);
// 将电灯开关命令,设置到遥控器上 0:代表位于第一组命令上
controller.setCommand(0,onCommand,offCommand);
// 按下 开电灯 按钮
controller.onBtnWasPushed(0);
// 按下 关电灯 按钮
controller.offBtnWasPushed(0);
// 按下 撤销 按钮
controller.undoBtnWasPushed();
System.out.println("--------------------------");
// 在遥控器上添加电视开关命令
TVReceiver receiver1 = new TVReceiver();
TVOnCommand onCommand1 = new TVOnCommand(receiver1);
TVOffCommand offCommand1 = new TVOffCommand(receiver1);
// 1:代表位于第二组命令上
controller.setCommand(1,onCommand1,offCommand1);
// 按下 开电视 按钮
controller.onBtnWasPushed(1);
// 按下 关电视 按钮
controller.offBtnWasPushed(1);
// 按下 撤销 按钮
controller.undoBtnWasPushed();
}
}
【例2】
服务员接收客户的订单内容,交给大厨制作。
服务员就是调用者角色,由她来发起命令;
大厨就是接收者角色,真正命令的执行对象;
订单:命令中包含订单
类图如下:
代码说明:
// 抽象命令角色
public interface Command {
public void execute();
}
// 具体命令类
public class OrderCommand implements Command {
public SeniorChef seniorChef;
public Order order;
public OrderCommand(SeniorChef seniorChef,Order order) {
this.seniorChef = seniorChef;
this.order = order;
}
@Override
public void execute() { // 实际调用接收者方法实现制作 makeFood()
System.out.println(order.diningTable+"号桌的订单");
Map<String, Integer> foodDir = order.getFoodDir();
Set<String> keySet = foodDir.keySet();
for (String foodName:keySet) {
seniorChef.makeFood(foodName,foodDir.get(foodName));
}
System.out.println(order.diningTable+"号桌的订单完成了");
}
}
// 订单类
public class Order {
// 餐桌号码
public int diningTable;
// 订单信息 餐品名+份数
private Map<String,Integer> foodDir = new HashMap<String,Integer>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodDir() {
return foodDir;
}
public void setFood(String foodName, int foodSize) {
foodDir.put(foodName,foodSize);
}
}
// 厨师类- 接收者角色
public class SeniorChef {
public void makeFood(String foodName,int foodSize){
System.out.println(foodSize+"份"+foodName+"正在制作");
}
}
// 服务员 - 调用者角色
public class Waitor {
// 持有多个命令
private List<Command> commands = new ArrayList<>();
public void setCommand(Command command){
// 添加命令到命令集中
commands.add(command);
}
// 发起命令功能,喊 订单来了
public void orderUp(){
System.out.println("美女服务员:新订单来了");
for (Command command : commands) {
if (command!=null){
command.execute();
}
}
}
}
测试:
public class Client {
public static void main(String[] args) {
Order order = new Order();
order.setDiningTable(1);
order.setFood("汉堡包",1);
order.setFood("薯条",1);
Order order2 = new Order();
order2.setDiningTable(2);
order2.setFood("炸鸡",1);
order2.setFood("快乐水",1);
SeniorChef receiver = new SeniorChef();
OrderCommand orderCommand = new OrderCommand(receiver,order);
orderCommand.execute();
System.out.println("-----------");
OrderCommand orderCommand2 = new OrderCommand(receiver,order2);
orderCommand2.execute();
}
}
优缺点
优点
- 降低系统的耦合度,命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便,采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”
- 可以实现宏命令,命令模式可以与组合模式先结合,将多个命令装配成一个组合命令,即宏命令
- 方便实现 undo-redo操作,命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
缺点
- 使用命令模式可能会造成某些系统有过多的具体命令类
- 系统结构更加复杂
使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求,将请求排队和执行请求。
- 系统需要支持命令的撤销和恢复操作。
JDK源码解析
Runnale是一个典型的命令模式,Runnable担当命令的角色,Thread充当的是调用者,start方法就是其执行方法。
/**
* @Author: mei_ming
* @DateTime: 2022/12/15 22:44
* @Description: Runnable
*
* Runnable:担当命令的角色,
* Thread : 充当的是调用者,
* start()是执行方法
*
* // 命令接口 - 抽象命令
* public interface Runnable {
* public abstract void run();
* }
*
* // 调用者
* public class Thread implements Runnable {
* private Runnable target; // 调用者持有命令对象
*
* public synchronized void start() { // 调用者执行start()方法,内部实际执行 Runnable.run()
* if (threadStatus != 0)
* throw new IllegalThreadStateException();
* group.add(this);
* boolean started = false;
* try {
* start0();
* started = true;
* } finally {
* try {
* if (!started) {
* group.threadStartFailed(this);
* }
* } catch (Throwable ignore) {
* }
* }
* }
* private native void start0();
* }
*
*/
// TurnOffThread 属于具体命令角色
class TurnOffThread implements Runnable{
private Receiver receiver; // 具体命令角色 持有接收者对象
public TurnOffThread(Receiver receiver){
this.receiver=receiver;
}
@Override
public void run() {
receiver.turnOFF();
}
}
// 接收者角色
class Receiver{
public void turnOFF(){
System.out.println("接收者调用turnOFF()方法");
}
}
// 测试
public class CommandTest {
public static void main(String[] args) {
TurnOffThread myThread = new TurnOffThread(new Receiver());
Thread thread = new Thread(myThread);
thread.start();
}
}
职责链模式
概述
学校OA系统的采购审批项目:需求是
- 采购员采购教学器材
- 如果金额小于等于5000,由教学主任审批
- 如果金额小于等于10000,由院长审批
- 如果金额小于等于30000,由副校长审批
- 如果金额超过30000以上,由校长审批
传统解决方案: - 接收到一个采购请求后,根据采购金额来调用对应的Approver(审批人)完成审批
传统解决方案的问题分析: - 客户端这里会使用分支判断来对不同的采购请求处理,这样就存在如下问题:
- 如果各个级别的人员审批金额发生变化,在客户端的也需要发生变化
- 客户端必须明确的知道 有多少个审批级别和访问
- 这样对一个采购请求进行处理和Approver(审批人)就存在强耦合关系,不利于代码的扩展和维护
- 解决方案:职责链模式
基本介绍
- 职责链模式又叫责任链模式,为请求创建一个接收者对象的链。这种模式对请求的发送者和接收者进行解耦。
- 职责链模式通常每个接收者多包含另一个接收者的引用,如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
- 这种类型的设计模式属于行为型模式。
结构
职责链模式主要包含以下角色:
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后续连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理,则处理请求,否则将该请求转给他的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
案例实现
对上述案例进行改进,类图如下:
代码实现:
// 发票-请求类
public class PurchaseRequest {
private int id = 0;
private int sum = 0; // 采购数量
private String context; // 采购器材名称
private float price = 0.0f; // 花费
public PurchaseRequest(int id, int sum, String context, float price) {
this.id = id;
this.sum = sum;
this.context = context;
this.price = price;
}
public int getId() {
return id;
}
public int getSum() {
return sum;
}
public String getContext() {
return context;
}
public float getPrice() {
return price;
}
}
// 抽象处理者
public abstract class Approver {
String name;
Approver nextApprover;
public Approver(String name) {
this.name = name;
}
public void setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
}
// 具体处理请求
protected abstract void processRequest(PurchaseRequest request);
}
// 教学主任 - 具体处理者角色
public class DepartmentApprover extends Approver {
public DepartmentApprover(String name) {
super(name);
}
@Override
protected void processRequest(PurchaseRequest request) {
float takeMoney = request.getPrice() * request.getSum();
if (takeMoney <= 5000) {
System.out.println(name + " 通过采购方案,方案编号:" + request.getId());
System.out.println("器材名:" + request.getContext());
System.out.println("器材开销:" + takeMoney);
} else {
//请求下一级处理
nextApprover.processRequest(request);
}
}
}
// 院长 - 具体处理者角色
public class CollegeApprover extends Approver {
public CollegeApprover(String name) {
super(name);
}
@Override
protected void processRequest(PurchaseRequest request) {
float takeMoney = request.getPrice() * request.getSum();
if (takeMoney > 5000 && takeMoney <= 10000) {
System.out.println(name + " 通过采购方案,方案编号:" + request.getId());
System.out.println("器材名:" + request.getContext());
System.out.println("器材开销:" + takeMoney);
} else {
//请求下一级处理
nextApprover.processRequest(request);
}
}
}
// 副校长 - 具体处理者角色
public class ViceSchoolMasterApprover extends Approver {
public ViceSchoolMasterApprover(String name) {
super(name);
}
@Override
protected void processRequest(PurchaseRequest request) {
float takeMoney = request.getPrice() * request.getSum();
if (takeMoney > 10000 && takeMoney <= 30000) {
System.out.println(name + " 通过采购方案,方案编号:" + request.getId());
System.out.println("器材名:" + request.getContext());
System.out.println("器材开销:" + takeMoney);
} else {
//请求下一级处理
nextApprover.processRequest(request);
}
}
}
// 校长 - 具体处理者角色
public class SchoolMasterApprover extends Approver {
public SchoolMasterApprover(String name) {
super(name);
}
@Override
protected void processRequest(PurchaseRequest request) {
float takeMoney = request.getPrice() * request.getSum();
if (takeMoney > 30000) {
System.out.println(name + " 通过采购方案,方案编号:" + request.getId());
System.out.println("器材名:" + request.getContext());
System.out.println("器材开销:" + takeMoney);
} else {
//请求下一级处理
nextApprover.processRequest(request);
}
}
}
测试:
public class Client {
public static void main(String[] args) {
PurchaseRequest request1 = new PurchaseRequest(1, 50, "篮球", 35);
PurchaseRequest request2 = new PurchaseRequest(2, 10, "篮球场", 1000);
PurchaseRequest request3 = new PurchaseRequest(3, 100, "足球", 150);
PurchaseRequest request4 = new PurchaseRequest(4, 2, "足球场", 20000);
// 构建职责链
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("王院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("李副校长");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("马校长");
// 连接
departmentApprover.nextApprover = collegeApprover;
collegeApprover.nextApprover = viceSchoolMasterApprover;
viceSchoolMasterApprover.nextApprover = schoolMasterApprover;
departmentApprover.processRequest(request1); // 由张主任审批
System.out.println("--------------------");
departmentApprover.processRequest(request2); // 由王院长审批
System.out.println("--------------------");
departmentApprover.processRequest(request3); // 由李副校长审批
System.out.println("--------------------");
departmentApprover.processRequest(request4); // 由马校长审批
}
}
【案例2】程序员请假问题:
- 请假一天以下,需要小组长同意;
- 请假1天-3天,需要部门经理同意;
- 请假7天以下,需要总经理同意;
类图如下:
代码实现:
// 请假条
public class LeaveRequest {
private String name; // 姓名
private int num; // 天数
private String context; // 内容
public LeaveRequest(String name, int num, String context) {
this.name = name;
this.num = num;
this.context = context;
}
public String getName() {
return name;
}
public int getNum() {
return num;
}
public String getContext() {
return context;
}
}
// 抽象处理者
public abstract class Handler {
protected final static int NUM_ONE = 1;
protected final static int NUM_THREE = 3;
protected final static int NUM_SEVEN = 7;
// 该领导处理的请求天数区间
private int numStart;
private int numEnd;
// 声明下一级领导
private Handler nextHandler;
public Handler(int numStart, int numEnd) {
this.numStart = numStart;
this.numEnd = numEnd;
}
public Handler(int numStart) {
this.numStart = numStart;
}
// 设置上级
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
// 各级领导处理请假条的方法
protected abstract void handleLeave(LeaveRequest leaveRequest);
// 提交请假条
public final void submit(LeaveRequest leaveRequest) {
// 该领导进行审批
this.handleLeave(leaveRequest);
if (this.nextHandler != null && leaveRequest.getNum() > this.numEnd) {
// 提交给上级领导进行审批
this.nextHandler.submit(leaveRequest);
} else {
System.out.println("流程结束~");
}
}
}
// 小组长类(具体处理者)
public class GroupLeader extends Handler {
public GroupLeader() {
super(0, Handler.NUM_ONE);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + " 请假 " + leaveRequest.getNum() + " 天," + leaveRequest.getContext() + ".");
System.out.println("小组长审批:同意");
}
}
// 经理类(具体处理者)
public class Manager extends Handler {
public Manager() {
super(Handler.NUM_ONE, Handler.NUM_THREE);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + " 请假 " + leaveRequest.getNum() + " 天," + leaveRequest.getContext() + ".");
System.out.println("经理审批:同意");
}
}
// 总经理类(具体处理者)
public class GeneralManager extends Handler {
public GeneralManager() {
super(Handler.NUM_THREE, Handler.NUM_SEVEN);
}
@Override
protected void handleLeave(LeaveRequest leaveRequest) {
System.out.println(leaveRequest.getName() + " 请假 " + leaveRequest.getNum() + " 天," + leaveRequest.getContext() + ".");
System.out.println("总经理审批:同意");
}
}
测试:
public class Client {
public static void main(String[] args) {
LeaveRequest request1 = new LeaveRequest("小明", 1, "感冒");
LeaveRequest request2 = new LeaveRequest("小红", 2, "重感冒");
LeaveRequest request3 = new LeaveRequest("小李", 7, "流感");
// 构建职责链
GroupLeader groupLeader = new GroupLeader();
Manager manager = new Manager();
GeneralManager generalManager = new GeneralManager();
// 连接
groupLeader.setNextHandler(manager);
manager.setNextHandler(generalManager);
groupLeader.submit(request1); // 由小组长审批
System.out.println("--------------------");
groupLeader.submit(request2); // 由小组长审批 + 经理审批
System.out.println("--------------------");
groupLeader.submit(request3); // 由小组长审批 + 经理审批 + 总经理审批
}
}
优缺点
优点:
- 降低了对象之间的耦合度
- 该模式降低了请求发送者和接收者的耦合度
- 增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则 - 增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可以动态地增加或删除责任 - 责任链简化了对象之间地连接
一个对象只需保持一个指向其后继者地引用,不需保持其他处理者地引用,这避免了使用众多地if 或者 if…else 语句。 - 责任分担
每个类只需要处理自己该处理地工作,不能处理地传递给下一个对象,明确各类的职责范围,符合类的单一职责原则
缺点:
- 不能保证每个请求一定被处理,由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理。
- 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定的影响。
- 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造出循环调用。
源码解析
暂无springMVC的filter链
状态模式
概述
【例】APP抽奖活动问题,具体要求如下:
- 假如没参加一次这个活动要扣除用户50积分,中奖概率为10%
- 奖品数量固定,抽完就不能抽奖
- 活动有四个状态,可以抽奖、不能抽奖、发放奖品和奖品领完
- 四个状态转化关系图如下所示
定义
- 状态模式(State Pattern):它主要用来解决对象在多种状态转换时,需要对外输出不同的行为的问题。状态和行为是一一对应的,状态之间可以相互转换
- 当一个对象的内在状态改变时,允许改变其行为,这个对象看起来像是改变了其类。
- 对有状态的对象,把复杂的判断逻辑提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。
结构
状态模式包含以下主要角色:
- 环境(Context)角色:也称为上下文,它定义了客户程序需要的接口,维护一个当前状态,并将与状态相关的操作委托给当前状态对象来处理。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为。
案例实现
App抽奖问题解决方案:
// 抽象状态类角色
public abstract class State {
// 扣除积分 -50
public abstract void deductMoney();
// 是否抽中奖品
public abstract boolean raffle();
// 发放奖品
public abstract void dispensePrize();
}
// 不可抽奖状态,最开始的状态
public class NoRaffleState extends State{
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public NoRaffleState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("扣除积分成功,您可以抽奖了");
activity.setState(activity.getCanRaffleState());
}
@Override
public boolean raffle() {
System.out.println("扣除积分后才能抽奖哦~");
return false;
}
@Override
public void dispensePrize() {
System.out.println("不能发放奖品");
}
}
// 能抽奖的状态
public class CanRaffleState extends State{
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public CanRaffleState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("已经扣过了积分");
}
@Override
public boolean raffle() {
System.out.println("正在抽奖,请稍后~");
Random r = new Random();
int num = r.nextInt(10);
if(num==0){ // 10%的中奖机会
// 改变活动状态为发放奖品
activity.setState(activity.getDispenseState());
return true;
}else{
System.out.println("很遗憾,没有抽中奖品~");
// 改变活动状态为 不能抽奖
activity.setState(activity.getNoRaffleState());
return false;
}
}
@Override
public void dispensePrize() {
System.out.println("没中奖,不能发放奖品");
}
}
// 发放奖品的状态
public class DispenseState extends State{
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public DispenseState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("不能扣除积分");
}
@Override
public boolean raffle() {
System.out.println("不能抽奖");
return false;
}
// 奖品数量大于0才发放奖品
@Override
public void dispensePrize() {
if(activity.getCount()>0){
System.out.println("恭喜中将了");
// 改变状态为不能抽奖
activity.setState(activity.getNoRaffleState());
}else{
System.out.println("很遗憾,奖品发送完了");
// 改变状态为奖品发送完毕
activity.setState(activity.getDispenseOutState());
}
}
}
// 奖品发放完毕状态-- 终止状态
public class DispenseOutState extends State{
// 初始化时传入活动引用,扣除积分后改变其状态
RaffleActivity activity;
public DispenseOutState(RaffleActivity activity) {
this.activity = activity;
}
@Override
public void deductMoney() {
System.out.println("奖品发送完了,请下次再参加");
}
@Override
public boolean raffle() {
System.out.println("奖品发送完了,请下次再参加");
return false;
}
@Override
public void dispensePrize() {
System.out.println("奖品发送完了,请下次再参加");
}
}
// 抽奖活动
public class RaffleActivity {
// 当前活动状态
State state = null;
// 奖品数量
int count = 0;
State noRaffleState = new NoRaffleState(this);
State canRaffleState = new CanRaffleState(this);
State dispenseState = new DispenseState(this);
State dispenseOutState = new DispenseOutState(this);
// 构造器
// 1. 初始化当前的状态
// 2. 设置奖品数量
public RaffleActivity(int count) {
this.state = getNoRaffleState();
this.count = count;
}
// 扣积分
public void deductMoney(){
state.deductMoney();
}
// 抽奖
public void raffle(){
// 如果中奖,则发放奖品
if (state.raffle()){
state.dispensePrize();
}
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public int getCount() {
int curCount = count;
count--;
return curCount;
}
public void setCount(int count) {
this.count = count;
}
public State getNoRaffleState() {
return noRaffleState;
}
public void setNoRaffleState(State noRaffleState) {
this.noRaffleState = noRaffleState;
}
public State getCanRaffleState() {
return canRaffleState;
}
public void setCanRaffleState(State canRaffleState) {
this.canRaffleState = canRaffleState;
}
public State getDispenseState() {
return dispenseState;
}
public void setDispenseState(State dispenseState) {
this.dispenseState = dispenseState;
}
public State getDispenseOutState() {
return dispenseOutState;
}
public void setDispenseOutState(State dispenseOutState) {
this.dispenseOutState = dispenseOutState;
}
}
测试
public class Client {
public static void main(String[] args) {
// 创建活动对象,奖品有一个
RaffleActivity raffleActivity = new RaffleActivity(1);
// 我们连续抽n次
for (int i = 1; i <=20 ; i++) {
System.out.println("--------第"+i+"次抽奖----------");
// 扣积分
raffleActivity.deductMoney();
// 抽奖
raffleActivity.raffle();
}
}
}
优缺点
优点
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为。
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块。
缺点
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当,将导致程序结构和代码的混乱
使用场景
- 当一个事件或对象有很多种状态,状态之间会相互转换,对不同的状态要求不同的行为的时候,可以考虑使用状态模式。
注意事项和细节
- 代码有很强的可读性。状态模式将每个状态的行为封装到对应的一个类中
- 方便维护。将容易产生问题的if-else-语句删除了,如果把每个状态的行为都放到一个类中,每次调用方法时都要判断当前是什么状态,不但会产出很多if-else-语句,而且容易出错。
- 符合“开闭原则”。容易增删状态。
- 会产生很多类。每个状态都要一个对应的类,当状态过多时会产生很多类,加大维护难度
观察者模式
概述
定义
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构
在观察者模式中,有如下角色:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里(如果是个接口,则这个集合在子类中实现),每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生变化时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcreteObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
案例实现
【例】微信公众号
在使用微信公众号时,大家都会有这样的体验,当你关注的公众号中有新的内容更新的话,它就会推送给关注公众号的微信用户端。我们使用观察者模式来模拟这样的场景,微信用户就是观察者,微信公众号就是被观察者,有多个微信用户关注了这个公众号。
类图如下:
代码实现:
// 抽象主题角色
public interface Subject {
//添加订阅者
void attach(Observer observer);
//删除订阅者
void detach(Observer observer);
//通知订阅者更新消息
void notify(String message);
}
// 具体主题
public class SubscriptionSubject implements Subject{
//存储订阅公众号的微信用户
private List<Observer> weixinUserList = new ArrayList<>();
@Override
public void attach(Observer observer) {
weixinUserList.add(observer);
}
@Override
public void detach(Observer observer) {
weixinUserList.remove(observer);
}
@Override
public void notify(String message) {
// 遍历通知所有订阅者
for (Observer observer:weixinUserList) {
observer.update(message);
}
}
}
// 抽象观察者角色
public interface Observer {
void update(String message);
}
// 具体观察者角色
public class WeiXinUser implements Observer{
private String name;
public WeiXinUser(String name){
this.name=name;
}
@Override
public void update(String message) {
System.out.println(name+"-"+message);
}
}
测试:
public class Client {
public static void main(String[] args) {
SubscriptionSubject subject = new SubscriptionSubject();
subject.attach(new WeiXinUser("孙悟空"));
subject.attach(new WeiXinUser("猪八戒"));
subject.attach(new WeiXinUser("沙和尚"));
subject.notify("师父被抓走了");
}
}
优缺点
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】
缺点
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃
使用场景
- 对象间存在一对多的关系,一个对象的状态发生变化会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时。
在源码中的体现
在Java中,通过java.util.Observable类和java.util.Observer接口定义了观察者模式,只要实现它们的子类就可以编写观察者模式示例。
- Observable类
Observable类时抽象目标类(被观察者),她有一个Vector集合成员变量,用于保存所有要通知的观察者对象,下面来介绍它最重要的3个方法:
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
// addObserver(o): 用于将新的观察者对象添加到集合中
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
// deleteObserver(o): 用于将元素删除
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
// 调用notifyObservers(arg),用于遍历所有观察者对象,调用update()方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!changed) // 判断标志
return;
arrLocal = obs.toArray();
clearChanged();
}
// 倒序通知
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
// 用来设置一个boolean类型的内部标志,注明目标对象发生了变化,当它为true时,notifyObservers()才会通知观察者。
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
}
- Observer接口
Observer接口时抽象观察者,它监视目标对象的变化,当目标对象发生变化时,观察者得到通知,并调用update方法,进行相应的工作。
public interface Observer {
void update(Observable o, Object arg);
}
【例】警察抓小偷
警察抓小偷也可以使用观察者模式来实现,警察是观察者,小偷是被观察者。
// 被观察者
public class Thief extends Observable {
private String name;
public Thief(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void steal(){
System.out.println("小偷:我偷东西了,有没有人来抓我~~~");
super.setChanged();
super.notifyObservers();
}
}
// 观察者
public class Policemen implements Observer {
private String name;
public Policemen(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("警察:"+((Thief) o).getName()+",我已经盯你很久了,可被我逮着了");
}
}
测试
public class ObservableTest {
public static void main(String[] args) {
Thief thief = new Thief("小明");
Policemen policemen = new Policemen("王警官");
thief.addObserver(policemen);
thief.steal();
}
}
中介者模式
概述
一般来说,同事类之间的关系是比较复杂的,多个同事类之间相互关联时,它们之间的关系会呈现为复杂的网状结构,这时一种过度耦合的架构,既不利于类的复用,也不稳定。例如在下左图中,有六个同事类对象,假如对象1发生变化,那么将会有4个对象受到影响。如果对象2发生变化,那么将会有5个对象受到影响。也就是说,同事类之间直接关联的设计是不好的。
如果引入中介者模式,那么同事类之间的关系将变为星型结构,从下右图中可以看到,任何一个类的变动,只会影响类的本身,以及中介者,这样就减少了系统的耦合。一个好的设计,必定不会把所有的对象关系处理逻辑封装在本类中,而是使用一个专门的类来管理这些不属于自己的行为。
定义
-
又叫调停模式,定义一个中介角色来封装一系列对象之间的交互,使原有对象之间的耦合松散,且可以独立地改变它们之间地交互。
-
解决的主要问题是子系统之间的解耦。
结构
中介者模式包含以下主要角色:
- 抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
- 具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个List来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
- 抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
- 具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
案例实现
【例】租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
类图如下:
代码如下:
// 抽象中介者
public abstract class Mediator {
public abstract void constact(String message,Person person);
}
// 具体中介者
public class MediatorStructure extends Mediator{
// 聚合房主和租房者对象
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public void constact(String message, Person person) {
if (person == houseOwner){
tenant.getMessage(message);
}else{
houseOwner.getMessage(message);
}
}
}
// 抽象同事类
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name,Mediator mediator){
this.name = name;
this.mediator = mediator;
}
}
// 具体同事类
public class Tenant extends Person {
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
// 和中介联系
public void constact(String message){
mediator.constact(message,this);
}
// 获取信息
public void getMessage(String message){
System.out.println("租房者:"+name+ " 获取到的信息是:"+message);
}
}
// 具体同事类
public class HouseOwner extends Person{
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
// 和中介联系
public void constact(String message){
mediator.constact(message,this);
}
// 获取信息
public void getMessage(String message){
System.out.println("房主:"+name+ " 获取到的信息是:"+message);
}
}
测试:
public class Client {
public static void main(String[] args) {
//创建中介者对象
MediatorStructure mediatorStructure = new MediatorStructure();
//创建租房者对象和房主对象
Tenant tenant = new Tenant("李四", mediatorStructure);
HouseOwner houseOwner = new HouseOwner("张三", mediatorStructure);
mediatorStructure.setHouseOwner(houseOwner);
mediatorStructure.setTenant(tenant);
tenant.constact("我要找个三室一厅的房间");
houseOwner.constact("我这里有一间三室一厅的房子");
}
}
优缺点
优点
- 松散耦合
中介者模式通过多个同事对象之间的交互封装到中介者对象里面,从而使得同事对象之间松散耦合,基本上可以做到互补依赖。这样一来,同事对象就可以独立地变化和复用,而不再像以前那样"牵一处而动全身"。 - 集中控制交互
多个同事对象地交互,被封装在中介者对象里面集中管理,使得这些交互行为发生变化地时候,只需要修改中介者就可以了,当然,如果是已经做好地系统,那么就扩展中介者对象,而各个同事类不需要做修改。 - 一对多关联转变为一对一的关联
没有使用中介者模式地时候,同事对象之间地关系通常是一对多的,引入中介者对象以后,中介者对象和同事对象的关系通常变成双向的一对一,这会让对象的关系更容易理解和实现。
缺点
- 当同事类太多时,中介者的职责将很大,他会变得复杂而庞大,以至于系统难以维护
使用场景
- 系统中对象之间存在复杂的引用关系,系统结构混乱且难以理解。
- 当想创建一个运行于多个类之间的对象,又不想生成新的子类时。
迭代器模式
概述
定义:
提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。
结构
迭代器模式主要包含以下角色:
- 抽象聚合(Aggregate)角色:定义存储,添加,删除聚合元素以及创建迭代器对象的接口
- 具体聚合(ConcrteAggregate)角色:实现抽象聚合类,返回一个具体迭代器的实例
- 抽象迭代器(Iterator)角色:定义访问和遍历聚合元素的接口,通常包含hasNext(),next()等方法
- 具体迭代器(ConcreteIterator)角色:实现抽象迭代器接口中所定义的方法,完成对聚合对象的遍历,记录遍历的当前位置
案例实现
【例】定义一个可以存储学生对象的容器对象,将遍历该容器的功能交由迭代器实现,涉及到的类如下:
// 聚合类对象
public class Student {
private String name;
private String number;
public Student(String name, String number) {
this.name = name;
this.number = number;
}
public Student() {
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", number='" + number + '\'' +
'}';
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
}
// 抽象聚合角色
public interface StudentAggregate {
// 添加学生功能
void addStudent(Student student);
// 删除学生功能
void removeStudent(Student student);
// 获取迭代器对象
StudentIterator getStudentIterator();
}
// 具体聚合角色
public class StudentAggregateImpl implements StudentAggregate{
private List<Student> list = new ArrayList<Student>();
@Override
public void addStudent(Student student) {
list.add(student);
}
@Override
public void removeStudent(Student student) {
list.remove(student);
}
@Override
public StudentIterator getStudentIterator() {
return new StudentIteratorImpl(list);
}
}
// 抽象迭代器角色
public interface StudentIterator {
// 判断是否还有元素
boolean hasNext();
// 获取下一个元素
Student next();
}
// 具体迭代器角色
public class StudentIteratorImpl implements StudentIterator{
private List<Student> list;
public StudentIteratorImpl(List<Student> list) {
this.list = list;
}
private int position = 0;
@Override
public boolean hasNext() {
return position<list.size();
}
@Override
public Student next() {
// 从集合中获取指定位置的元素
Student student = list.get(position);
position++;
return student;
}
}
测试
public class Client {
public static void main(String[] args) {
StudentAggregateImpl studentAggregate = new StudentAggregateImpl();
studentAggregate.addStudent(new Student("张三","001"));
studentAggregate.addStudent(new Student("李四","002"));
studentAggregate.addStudent(new Student("王五","003"));
studentAggregate.addStudent(new Student("赵六","004"));
// 1. 获取迭代器对象
StudentIterator studentIterator = studentAggregate.getStudentIterator();
// 2. 遍历
while(studentIterator.hasNext()){
// 3. 获取元素
Student student = studentIterator.next();
System.out.println(student);
}
}
}
优缺点
优点
- 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
- 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
- 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器都很方便,无须修改原有代码,满足“开闭原则”的要求。
缺点 - 增加了类的个数,这在一定程度上增加了系统的复杂性
使用场景
- 当需要为聚合对象提供多种遍历方式时。
- 当需要为遍历不同的聚合结构提供一个统一的接口时。
- 当访问一个聚合对象的内容而无须暴露其内部细节的表示时。
JDK源码解析
迭代器模式在Java的很多集合类中被广泛应用,接下来看看Java源码中是如何使用迭代器模式的。
List<String> list = new ArrayList<>();
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
看完这段代码是不是很熟悉,与我们上面代码基本类似。单列集合都使用到了迭代器,我们以ArrayList来举例说明:
// 1. List 抽象聚合类
public interface List<E> extends Collection<E> {
Iterator<E> iterator();
boolean add(E e);
boolean remove(Object o);
// ......
}
// 2. ArrayList 具体的聚合类,实现List中的方法
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
// 返回的是实现了Iterator接口的具体迭代器对象 new Itr()
public Iterator<E> iterator() {
return new Itr();
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
// 具体迭代器类
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {
}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
// 3. Iterator 抽象迭代器
public interface Iterator<E> {
boolean hasNext();
E next();
// .....
}
// 4. 具体迭代器是ArrayList中实现Iterator接口的内部类,Itr
在ArrayList中,大致就是在iterator()方法中返回了一个实例化的Iterator对象。Itr是一个内部类,它实现了Iterator接口,并重写了其中的抽象方法。
注意:
当我们在使用JAVA开发时,想使用迭代器模式的话,只要让我们自己定义的容器类实现java.util.Iterable
并实现其中的iterator()方法使其返回一个java.util.Iterator
的实现类就可以了。
访问者模式
概述
定义:
封装一些用于某种数据结构中的各元素的操作,它可以在不改变这个数据结构的前提下定义作用于这些元素的新的操作。
结构
访问者模式包含以下主要角色:
- 抽象访问者(Visitor)角色:定义了对一个元素(Element)访问的行为,他的参数就是可以访问的元素,它的方法个数理论上来讲与元素类个数(Element的实现类个数)是一样的,从这点不难看出,访问者模式要求元素类的个数不能改变。
- 具体访问者(ConcreteVisitor)角色:给出对每个元素类访问时所产生的具体行为。
- 抽象元素(Element)角色:定义了一个接受访问者的方法(accept),其意义是指,每个元素都可以被访问者访问
- 具体元素(ConcreteElement)角色:提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法
- 对象结构(Object Structure)角色:定义当中所提到的对象结构,对象结构是一个抽象表述,具体点可以理解为一个具有容器性质或复合对象特性的类,它会含有一组元素(Element),并且可以迭代这些元素,供访问者访问。
案例实现
【例】给宠物喂食
现在养宠物的人特别多,宠物分为猫,狗,给宠物喂食的话,主人可以喂,其他人也可以喂。
- 抽象访问者角色:给宠物喂食的人
- 具体访问者角色:主人,其他人
- 抽象元素角色:宠物
- 具体元素角色:猫,狗
- 结构对象角色:主人家,(包含宠物的对象)
类图如下:
代码实现:
// 抽象访问者角色
public abstract class Person {
public String name;
Person(String name) {
this.name = name;
}
abstract void feed(Cat cat);
abstract void feed(Dog dog);
}
// 具体访问者角色
public class Owner extends Person{
Owner(String name) {
super(name);
}
@Override
public void feed(Cat cat) {
System.out.println("主人("+name+")喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println("主人("+name+")喂狗");
}
}
// 具体访问者角色
public class Someone extends Person{
Someone(String name) {
super(name);
}
@Override
public void feed(Cat cat) {
System.out.println("其他人("+name+")喂猫");
}
@Override
public void feed(Dog dog) {
System.out.println("其他人("+name+")喂狗");
}
}
// 抽象元素角色接口
public interface Animal {
void accept(Person person);
}
// 具体元素角色类
public class Cat implements Animal{
@Override
public void accept(Person person) {
person.feed(this); // 接受访问者的喂食
System.out.println("猫咪:好好吃,谢谢你:"+person.name);
}
}
// 具体元素角色类
public class Dog implements Animal{
@Override
public void accept(Person person) {
person.feed(this); // 接受访问者的喂食
System.out.println("狗子:好好吃,谢谢你:"+person.name);
}
}
// 对象结构类角色
public class Home {
// 声明一个集合对象,用来存储元素对象
private List<Animal> list = new ArrayList<Animal>();
// 添加
public void add(Animal animal){
list.add(animal);
}
// 集体喂食
public void action(Person person){
System.out.println("喂食开始");
for (Animal animal : list) {
animal.accept(person);
}
System.out.println("喂食结束");
}
}
测试
public class Client {
public static void main(String[] args) {
// 创建Home对象(小明的家)
Home home = new Home();
// 添加宠物
home.add(new Dog());
home.add(new Cat());
home.action(new Owner("小明"));
System.out.println("-------------");
home.action(new Someone("小红"));
}
}
优缺点
优点
- 扩展性好
在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能(溜宠物,逗宠物) - 复用性好
通过访问者来定义整个对象结构通用的功能,从而提高复用程度(在对象结构类中封装了喂食的功能) - 分离无关行为
通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
缺点
- 对象结构变化很困难
在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者中增加相应的具体操作,这违背了"开闭原则"。 - 违反了依赖倒置原则
访问者模式依赖了具体类,而没有依赖抽象类。(feed()方法使用具体的Cat,Dog,而不是抽象)
使用场景
- 对象结构相对稳定,但其操作算法经常变化的程序
- 对象结构中的对象需要提供不同且不相关的操作,而且要避免让这些操作的变化影响对象的结构
扩展
访问者模式用到了一种双分派的技术
1.分派
变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型;而变量所引用的对象的真实类型又叫做变量的实际类型。比如Map map = new HashMap()
,map变量的静态类型时Map
,实际类型是HashMap
。根据对象的类型而对方法进行选择,就是分派(Dispatch),分派又分为静态分派和动态分派。
静态分派: 发生在编译时期,分派根据静态类型信息发生。静态分派对于我们来说并不陌生,方法重载就是静态分派。
动态分派:发生在运行时期,动态分派动态地置换掉某个方法。Java通过方法地重写支持动态分派。
动态分派
(方法重写)
public class Animal {
public void execute(){
System.out.println("Animal");
}
}
public class Cat extends Animal {
@Override
public void execute() {
System.out.println("Cat");
}
}
public class Dog extends Animal {
@Override
public void execute() {
System.out.println("Dog");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Dog();
Animal b = new Cat();
a.execute(); // Dog
b.execute(); // Cat
}
}
上面代码的结果大家应该直接可以说出来,这不就是多态吗! 运行执行的是子类中的方法。
Java编译期在编译时期并不总是知道哪些代码会被执行,因为编译器仅仅知道对象的静态类型,而不知道对象的真实类型;而方法的调用则是根据对象的真实类型,而不是静态类型。
静态分派
(方法重载)
public class Animal {
}
public class Cat extends Animal{
}
public class Dog extends Animal{
}
public class Execute {
public void execute(Animal animal){
System.out.println("Animal");
}
public void execute( Cat cat){
System.out.println("cat");
}
public void execute(Dog dog){
System.out.println("dog");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
exe.execute(a); // Animal
exe.execute(a1); // Animal
exe.execute(a2); // Animal
}
}
为什么会打印出animal呢?
重载方法的分派是根据静态类型进行的,这个分派过程在编译时期就完成了。
双分派
所谓的双分派技术就是在选择一个方法的时候,不仅仅要根据消息接收者的运行时区分,还要根据参数的运行时区分。
public class Animal {
public void accept(Execute exe){
exe.execute(this);
}
}
public class Cat extends Animal {
public void accept(Execute exe){
exe.execute(this);
}
}
public class Dog extends Animal {
public void accept(Execute exe){
exe.execute(this);
}
}
public class Execute {
public void execute(Animal animal){
System.out.println("Animal");
}
public void execute( Cat cat){
System.out.println("cat");
}
public void execute(Dog dog){
System.out.println("dog");
}
}
public class Client {
public static void main(String[] args) {
Animal a = new Animal();
Animal a1 = new Dog();
Animal a2 = new Cat();
Execute exe = new Execute();
a.accept(exe); // Animal
a1.accept(exe); // dog
a2.accept(exe); // cat
}
}
在上面代码中,客户端将Execute对象作为参数传递给Animal类型的变量调用方法,这里完成第一次分派,这里是方法重写,所以是动态分派,也就是执行实际类型中的方法,同时也将this作为参数传递进去,这里就完成了第二次分派,这里的Execute类中有多个重载的方法,而传递进行的是this,就是具体的实际类型的对象。
双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以此时重载也是动态的了。
备忘录模式
概念
备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定地历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来地备忘录将状态复原,很多软件都提供了撤销(Undo)操作,如Word、记事本等软件在编辑时按Ctrl+Z 组合键能撤销当时操作,使文档恢复到之前地状态,还有在浏览器中地后退键,数据库事务管理中地回滚操作。
定义:
又叫快照模式,在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后需要时能将该对象恢复到原先保存的状态。
结构
备忘录模式的主要角色:
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能。它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录有两个等效的接口
窄接口:管理者对象(和其他发起人对象之外的任何对象)看到的是备忘录的窄接口,这个窄接口只允许他把备忘录对象传给其他的对象。
宽接口:与管理者看到的窄接口相反,发起人对象可以看到一个宽接口,这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
案例实现
【例】游戏挑战BOSS
游戏中的某个场景,一游戏角色有生命力、攻击力、防御力等数据,在打BOSS前和后一定会不一样的,我们允许玩家复活。(恢复到之前的状态)
要实现上述案例,有两种方式:
- “白箱”备忘录模式
- “黑箱”备忘录模式
白箱备忘录模式
备忘录角色对任何对象都提供一个接口,即宽接口,备忘录角色的内部所存储的状态就对所有对象公开。类图如下:
代码如下:
// 发起人角色
public class GameRole {
private int vit; // 生命力
private int atk; // 攻击力
private int def; // 防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
// 保存自己当前状态
public RoleStateMemento saveState() {
return new RoleStateMemento(vit, atk, def);
}
// 恢复角色状态
public void recoverState(RoleStateMemento memento) {
this.vit = memento.getVit();
this.atk = memento.getAtk();
this.def = memento.getDef();
}
// 展示当前状态
public void displayState(){
System.out.println("生命力:"+vit);
System.out.println("攻击力:"+atk);
System.out.println("防御力:"+def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
// 备忘录角色类
public class RoleStateMemento {
private int vit; // 生命力
private int atk; // 攻击力
private int def; // 防御力
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public RoleStateMemento() {
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
// 管理备忘录角色
public class RoleStateCaretaker {
private RoleStateMemento roleStateMemento;
public RoleStateMemento getRoleStateMemento() {
return roleStateMemento;
}
public void setRoleStateMemento(RoleStateMemento roleStateMemento) {
this.roleStateMemento = roleStateMemento;
}
public RoleStateCaretaker() {
}
}
测试:
public class Client {
public static void main(String[] args) {
// 创建游戏角色对象
GameRole role = new GameRole();
role.initState();
role.displayState();
RoleStateMemento state = role.saveState();
RoleStateCaretaker caretaker = new RoleStateCaretaker();
caretaker.setRoleStateMemento(state); // 存档
System.out.println("-----遇到BOSS------");
role.setAtk(50);
role.setVit(50);
role.setDef(50);
role.displayState();
System.out.println("-----复活-----");
role.recoverState(state);
role.displayState();
}
}
黑箱备忘录模式
备忘录角色对发起人对象提供一个宽接口,而为其他对象提供一个窄接口。在Java语言中,实现双重接口的办法就是将备忘录类设计成发起人类的内部成员类
将RoleStateMemento
设为GameRole
的内部类,从而将RoleStateMemento
对象封装在GameRole
里面,在外面提供一个标识接口Memento
给RoleStateCaretaker
及其他对象使用。这样GameRole
类看到的是RoleStateMemento
所有的接口,而RokeStateCaretaker
及其他对象看到的仅仅是标志接口Memento
所暴露出来的接口。从而维护了封装性。类图如下:
代码如下:
// 备忘录接口
public interface Memento {
}
// 发起者-游戏对象角色
public class GameRole {
private int vit; // 生命力
private int atk; // 攻击力
private int def; // 防御力
//初始化内部状态
public void initState() {
this.vit = 100;
this.atk = 100;
this.def = 100;
}
// 保存自己当前状态
public Memento saveState() {
return new RoleStateMemento(vit, atk, def);
}
// 恢复角色状态
public void recoverState(Memento memento) {
RoleStateMemento memento2 = (RoleStateMemento)memento;
this.vit = memento2.getVit();
this.atk = memento2.getAtk();
this.def = memento2.getDef();
}
// 展示当前状态
public void displayState(){
System.out.println("生命力:"+vit);
System.out.println("攻击力:"+atk);
System.out.println("防御力:"+def);
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
private class RoleStateMemento implements Memento{
private int vit; // 生命力
private int atk; // 攻击力
private int def; // 防御力
public RoleStateMemento() {
}
public RoleStateMemento(int vit, int atk, int def) {
this.vit = vit;
this.atk = atk;
this.def = def;
}
public int getVit() {
return vit;
}
public void setVit(int vit) {
this.vit = vit;
}
public int getAtk() {
return atk;
}
public void setAtk(int atk) {
this.atk = atk;
}
public int getDef() {
return def;
}
public void setDef(int def) {
this.def = def;
}
}
}
// 备忘录对象管理角色类
public class RoleStateCaretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
测试:
public class Client {
public static void main(String[] args) {
// 创建游戏角色对象
GameRole role = new GameRole();
role.initState();
role.displayState();
Memento state = role.saveState();
RoleStateCaretaker caretaker = new RoleStateCaretaker();
caretaker.setMemento(state); // 存档
System.out.println("-----遇到BOSS------");
role.setAtk(50);
role.setVit(50);
role.setDef(50);
role.displayState();
System.out.println("-----复活-----");
role.recoverState(state);
role.displayState();
}
}
优缺点
优点
- 提供了一种可以恢复状态的机制,当用户需要时能够比较方便地将数据恢复到某个历史地状态。
- 实现了内部状态地封装。除了创建它地发起人之外,其他对象都不能访问这些状态信息。
- 简化了发起人。发起人不需要管理和保护其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
缺点
- 资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
使用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景
解释器模式
概述
我们想要设计一个软件用来进行加减法计算,我们第一想法就是建一个工具类
public static int add(int a,int b){
return a+b;
}
上面的形式比较单一、有限(只能实现两数相加),如果形式变化非常多,就不符合要求。
新要求:1+2+3-4-5
显然,现在需要一种翻译识别机器,能够解析由数字以及符号过程的表达式。如果把运算符和数字都看作节点的话,能够逐个节点的进行读取解析运算,这就是解释器模式的思维。
定义:
给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
在解释器模式中,我们需要将待解决的问题,提取出规则,抽象为一种“语言”。比如家减法运算,规则:由数值和+ - 符号组成的合法序列,“1+2-3”
文法规则:
文法是用于描述语言的语法结构的形式规则。
expression ::= value | plus | minus
plus ::= expression '+' expression
minus ::= expression '-' expression
value ::= integer
上述规则描述为:
表达式可以是一个值,也可以是plus或minus运算,而plus和minus又是由表达式结合运算符构成的,值的类型为整数型。
抽象语法树:
是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上每个节点都表示源代码中的一种结构。
结构
解释器模式包含以下主要角色:
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器操作,主要包含解释方法 interpret()
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之对应。
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
案例实现
【例】设计实现加减法的软件
代码如下:
// 抽象表达式(Abstract Expression)角色
public abstract class AbstractExpression {
// 解析表达式并返回计算结果
public abstract int interpret(Context context);
}
// 变量类
public class Variable extends AbstractExpression{
// 声明存储变量名的成员变量
private String name;
public void setName(String name) {
this.name = name;
}
public Variable(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
return context.getValue(this);
}
@Override
public String toString() {
return name;
}
}
// 加法表达式类
public class Plus extends AbstractExpression{
// +号左边的表达式
private AbstractExpression left;
// +号右边的表达式
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
// 将左边表达式的结果与右边表达式的结果相加
return left.interpret(context) + right.interpret(context);
}
@Override
public String toString() {
return "("+left.toString()+" + "+right.toString()+")";
}
}
// 减法表达式类
public class Minus extends AbstractExpression{
// -号左边的表达式
private AbstractExpression left;
// -号右边的表达式
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
// 将左边表达式的结果与右边表达式的结果相减
return left.interpret(context) - right.interpret(context);
}
@Override
public String toString() {
return "("+left.toString()+" - "+right.toString()+")";
}
}
// 环境角色类
public class Context {
// 定义一个map集合,用来存储变量名及对应的值
private Map<Variable,Integer> map = new HashMap<>();
// 添加变量,并为变量赋值
public void assign(Variable var ,Integer value){
map.put(var,value);
}
// 根据变量获取对应的值
public int getValue(Variable var){
return map.get(var);
}
}
测试:
public class Client {
public static void main(String[] args) {
// 创建环境对象
Context context = new Context();
// 创建多个变量对象
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
// 将变量存储到环境对象中
context.assign(a, 1);
context.assign(b, 2);
context.assign(c, 3);
context.assign(d, 4);
// 获取抽象语法树 (a - ((b - c) + d)) = 1-3 = -2
AbstractExpression expression = new Minus(a, new Plus(new Minus(b, c), d));
// 解析语法树的内容-计算结果
int i = expression.interpret(context);
System.out.println(expression + " = " + i);
}
}
优缺点
优点:
-
易于改变和扩展文法
由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单地语言。 -
实现文法较为容易
在抽象语法树中每一个表达式节点 -
增加新的解释表达式较为方便
如果用户需要新的解释表达式只需要对应增加一个新的终结表达式或非终结表达式类,原有的表达式类代码无须修改,符合"开闭原则"
缺点:
- 对于复杂文法难以维护。
在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加。 - 执行效率较低
由于在解释器模式中大量使用循环和递归调用,因此在解释较为复杂的句子时,其速度很慢
使用场景
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语句来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候。