05-行为型模式(11)
- 用于描述程序在运行时复杂的流程控制,即描述多个类、对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,涉及算法、对象间职责分配
- 行为型模式分为类行为模式、对象行为模式
- 前者采用继承机制在类间分派行为
- 后者采用组合、聚合在对象间分配行为。由于组合关系、聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性
行为型模式分为:
- 模板方法模式(类):Template Method Pattern
- 策略模式:Strategy Pattern
- 责任链模式:Chain Of Responsibility Pattern
- 观察者模式:Observer Pattern
- 状态模式:State Pattern
- 访问者模式:Visitor Pattern
- 命令模式:Command Pattern
- 中介者模式:Mediator Pattern
- 备忘录模式:Memento Pattern
- 迭代器模式:Iterator Pattern
- 解释器模式(类):Interpreter Pattern
1. 模板方法模式
1. 介绍
- 模板方法模式(Template Method Pattern)原始定义:在操作中定义算法的框架,将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤
- 模板方法中的算法可以理解为广义上的业务逻辑,并不是特指某一个实际的算法。定义中所说的算法的框架就是模板,包含算法框架的方法就是模板方法
eg:去医院看病一般要经过4个流程:挂号、取号、排队、医生问诊等,其中挂号、 取号 、排队对每个病人是一样的,可以在父类中实现,但是具体医生如何根据病情开药每个人都是不一样的,所以开药这个操作可以延迟到子类中实现
- 模板方法模式是一种基于继承的代码复用技术,它是一种类行为模式。其结构中只存在父类与子类之间的继承关系
- 模板方法提高程序的复用性、扩展性:
- 复用:所有的子类可以复用父类中提供的模板方法代码
- 扩展:框架通过模板模式提供功能扩展点,让框架用户可以在不修改框架源码的情况下,基于扩展点定制化框架的功能
2. 原理
- 模板方法模式定位很清楚,就是为了解决算法框架这类特定的问题,同时明确表示需要使用继承结构
- 抽象父类:定义一个算法所包含的所有步骤,并提供一些通用的方法逻辑
- 具体子类:继承自抽象父类,根据需要重写父类提供的算法步骤中的某些步骤
抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法、若干个基本方法构成
- 模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法
- 基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。分为三种:
- 抽象方法(Abstract Method) :由抽象类声明、由其具体子类实现
- 具体方法(Concrete Method) :由一个抽象类或具体类声明并实现,其子类可以进行重写也可以直接继承
- 钩子方法(Hook Method) :方法名一般为
isXxx()
,返回boolean类型- 在抽象类中已经实现,用于判断的逻辑方法
- 需要子类重写的空方法
3. 实现
/**
* 抽象父类
*/
public abstract class AbsTemplate {
void step1(String key) {
System.out.println("在模板类中 -> 执行步骤1");
if (step2(key)) {
step3();
} else {
step4();
}
step5();
}
boolean step2(String key) {
System.out.println("在模板类中 -> 执行步骤2");
return "x".equals(key);
}
abstract void step3();
abstract void step4();
void step5() {
System.out.println("在模板类中 -> 执行步骤5");
}
public void run(String key) {
step1(key);
}
}
public class ConcreteA extends AbsTemplate {
@Override
void step3() {
System.out.println("在子类A中 -> 执行步骤3");
}
@Override
void step4() {
System.out.println("在子类A中 -> 执行步骤4");
}
}
public class ConcreteB extends AbsTemplate {
@Override
void step3() {
System.out.println("在子类B中 -> 执行步骤3");
}
@Override
void step4() {
System.out.println("在子类B中 -> 执行步骤4");
}
}
@Test
public void test1() {
AbsTemplate concreteClassA = new ConcreteA();
concreteClassA.run("x");
System.out.println("================");
AbsTemplate concreteClassB = new ConcreteB();
concreteClassB.run("");
}
4. 应用实例
1. AbsAccount
P2P公司的借款系统中有一个利息计算模块,利息的计算流程:
- 用户登录系统,登录时需要输入账号密码,如果登录失败(用户密码错误),系统需要给出提示
- 如果用户登录成功,则根据用户的借款的类型不同,使用不同的利息计算方式进行计算
- 系统需要显示利息
/**
* 账户抽象类
*/
public abstract class AbsAccount {
// step1: 验证用户信息是否正确
public boolean validate(String account, String password) {
LogUtil.info("账号: ", account, ", 密码: ", password);
return account.equalsIgnoreCase("tom") && password.equalsIgnoreCase("123456");
}
// step2: 计算利息
public abstract void calculate();
// step3: 显示利息
public void display() {
System.out.println("显示利息!");
}
// 模板方法
public void handle(String account, String password) {
if (!validate(account, password)) {
System.out.println("账户或者密码错误!");
return;
}
calculate();
display();
}
}
/**
* 借款七天
*/
public class LoanSevenDays extends AbsAccount {
@Override
public void calculate() {
System.out.println("借款周期7天, 无利息! 仅收取贷款金额的1%的服务费");
}
// 勾子方法。可以重写
@Override
public void display() {
System.out.println("七日借款内无利息!");
}
}
/**
* 借款有一个月
*/
public class LoanOneMonth extends AbsAccount {
@Override
public void calculate() {
System.out.println("借款周期30天, 利息为借款总额的10%");
}
}
@Test
public void test2() {
AbsAccount a1 = new LoanSevenDays();
a1.handle("tom", "123456");
System.out.println("===================");
AbsAccount a2 = new LoanOneMonth();
a2.handle("tom", "123456");
}
4. AbsTemplateLogin
public abstract class AbsTemplateLogin {
/**
* final不能子类重写
*/
public final boolean checkLogin(LoginBean loginBean) {
User user = findUser(loginBean.getLoginName());
if (user != null) {
return checkPassword(user.getPassword(), encrypt(loginBean.getPassword()));
}
return false;
}
private boolean checkPassword(String userPassword, String inputPassword) {
return userPassword.equals(inputPassword);
}
public abstract String encrypt(String password);
public abstract User findUser(String loginName);
}
public class LoginDefault extends AbsTemplateLogin {
@Override
public User findUser(String loginName) {
// 按照用户名登录的查询
return new User("123", "admin", "123456", "管理员");
}
@Override
public String encrypt(String password) {
// MD5算法
return password;
}
}
public class LoginEmail extends AbsTemplateLogin {
@Override
public User findUser(String loginName) {
// 按照邮箱登录的查询
return new User("123", "admin", "12345678", "管理员");
}
@Override
public String encrypt(String password) {
// MD5或者RSA
return password;
}
}
@Test
public void fang() {
LoginBean lb = new LoginBean("admin", "123456");
AbsTemplateLogin template = new LoginDefault();
// AbsTemplateLogin template = new EmailLoginTemplate();
System.out.println(
template.checkLogin(lb)
);
}
5. 总结
1. 优点
- 在父类中,形式化的定义算法,由子类来实现细节处理。子类实现详细的处理代码时,并不会改变父类算法中步骤的执行顺序
- 模板方法可以实现一种反向的控制结构,通过子类覆盖父类的钩子方法,来决定某一个特定步骤是否需要执行
- 在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责、开闭原则
2. 缺点
- 对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象
- 父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度
3. 使用场景
- 多个类有相同的方法并且逻辑可以共用时
- 将通用的算法或固定流程设计为模板,在每一个具体的子类中再继续优化算法步骤或流程步骤时
- 重构超长代码时,发现某一个经常使用的公有方法
2. 策略模式
- 策略模式(Strategy Pattern)原始定义:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化
1. 概述
现实生活中,常常遇到实现某种目标存在多种策略可供选择的情况
- eg:出行旅游可以坐飞机、坐火车、骑自行车、自己开私家车...
- eg:网购可以选择工商银行、农业银行、建设银行,它们提供的算法都是一致的,就是付款
在软件开发中,当实现某一个功能存在多种算法或策略。可以根据环境或条件的不同,选择不同的算法、策略来完成该功能
- 可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法
- 也可以将这些算法都封装在一个统一的方法中,使用
if...else...
等条件判断语句进行选择
这两种方式都存在硬编码问题,后期需要增加算法就需要修改源代码。可拓展性低
2. 原理
- 策略模式中,可以定义一些独立类来封装不同的算法,每一个类封装一种具体算法,被称为一种策略类
- 为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明
- 抽象策略类(Strategy):通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口
- 具体策略类(Concrete Strategy):提供具体的算法实现或行为
- 环境或上下文类(Context):是使用算法的角色,持有一个策略类的引用,最终给客户端调用
3. 实现
- 策略模式的本质是通过 Context类来作为中心控制单元,对不同的策略进行调度分配
/**
* 抽象策略类
*/
public interface IStrategy {
void algorithm();
}
/**
* 具体策略类
*/
public class StrategyA implements IStrategy {
@Override
public void algorithm() {
System.out.println("执行策略A");
}
}
public class StrategyB implements IStrategy {
@Override
public void algorithm() {
System.out.println("执行策略B");
}
}
/**
* 上下文类: 策略模式的本质就是通过 **Context类作为控制单元**, 对不同的策略进行调度分配
*/
public class Context {
// 维持一个抽象策略的引用
private final IStrategy strategy;
public Context(IStrategy strategy) {
this.strategy = strategy;
}
// 调用策略类中的算法
public void algorithm() {
strategy.algorithm();
}
}
@Test
public void test1() {
// IStrategy strategy = new StrategyA();
IStrategy strategy = new StrategyB();
Context context = new Context(strategy);
context.algorithm();
}
4. 应用实例
- 面试:如何用设计模式消除代码中的
if-else
- 物流行业中,每发送一份EDI报文(XML格式文件),后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)
- 回执类型:MT1101、MT2101、MT4101、MT8104,会执行对应的业务逻辑处理
1. Receipt
1. 常规写法
/**
* 回执信息
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Receipt {
// 回执内容
private String message;
// 回执类型: MT1101、MT2101、MT4101、MT8104
private String type;
}
/**
* 回执信息生成类
*/
public class ReceiptBuilder {
public static List<Receipt> getReceiptList() {
// 模拟回执信息
List<Receipt> list = new ArrayList<>();
// MT1101、MT2101、MT4101、MT8104
list.add(new Receipt("MT1101回执信息", "MT1101"));
// list.add(new Receipt("MT2101回执信息", "MT2101"));
// list.add(new Receipt("MT4101回执信息", "MT4101"));
// list.add(new Receipt("MT8104回执信息", "MT8104"));
// ...
return list;
}
}
@Test
public void test2() {
List<Receipt> receiptList = ReceiptBuilder.getReceiptList();
// 回执类型: MT1101、MT2101、MT4101、MT8104
for (Receipt receipt : receiptList) {
if ("MT1011".equals(receipt.getType())) {
System.out.println("接收到MT1011的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑A......");
} else if ("MT2101".equals(receipt.getType())) {
System.out.println("接收到MT2101的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑B......");
} else if ("MT4101".equals(receipt.getType())) {
System.out.println("接收到MT4101的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑C......");
} else if ("MT8104".equals(receipt.getType())) {
System.out.println("接收到MT8104的回执信息");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑D......");
}
// ...
}
}
2. 策略模式重构
- 通过策略模式,将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端
/**
* 回执处理策略接口
*/
public interface ISttgReceipt {
void handle(Receipt receipt);
}
/**
* 具体策略类
*/
public class SttgMT1101 implements ISttgReceipt {
@Override
public void handle(Receipt receipt) {
System.out.println("解析报文MT1101: " + receipt.getMessage());
}
}
public class SttgMT2101 implements ISttgReceipt {
@Override
public void handle(Receipt receipt) {
System.out.println("解析报文 MT2101: " + receipt.getMessage());
}
}
- 策略工厂
- 经过重构,已经消除了
if-else
结构,每当新来一种回执,只需要添加新的回执处理策略,并修改SttgReceiptFactory
中的Map集合- 符合开闭原则,通过反射获取策略类,放到字典Map中
/**
* 策略工厂类
*/
public class SttgReceiptFactory {
public SttgReceiptFactory() {
}
// 使用Map集合存储策略信息, 彻底的消除 if...else
private static Map<String, ISttgReceipt> strategyMap;
// 初始化具体策略, 保存到map集合
public static void init() {
strategyMap = new HashMap<>();
strategyMap.put("MT1101", new SttgMT1101());
strategyMap.put("MT2101", new SttgMT2101());
}
public static void init1() {
strategyMap = new HashMap<>();
try {
// 从外部配置文件读取
String file = "/Users/listao/mca/listao_boot/design_pattern/src/main/resources/config.xml";
SAXReader reader = new SAXReader();
Document document = reader.read(file);
Node node = document.selectSingleNode("/config/className");
String className = node.getText();
Class<?> clazz = Class.forName(className);
ISttgReceipt strategy = (ISttgReceipt) clazz.newInstance();
strategyMap.put("MT1101", strategy);
} catch (Exception e) {
e.printStackTrace();
}
}
// 根据回执类型, 获取对应的策略对象
public static ISttgReceipt getStrategy(String receiptType) {
return strategyMap.get(receiptType);
}
}
/resources/config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<className name="MT1101">com.listao.dp._14_strategy.example02.SttgMT1101</className>
</config>
- 策略上下文类(策略接口持有者)
/**
* 上下文类, 持有策略接口, 决定执行哪一个具体的策略类
*/
public class SttgReceiptContext {
private ISttgReceipt sttgReceipt;
public void setSttgReceipt(ISttgReceipt sttgReceipt) {
this.sttgReceipt = sttgReceipt;
}
// 调用策略类中方法
public void handle(Receipt receipt) {
if (receipt != null) {
sttgReceipt.handle(receipt);
}
}
}
- 客户端
@Test
public void test3() {
// 模拟回执
List<Receipt> receiptList = ReceiptBuilder.getReceiptList();
// 策略上下文
SttgReceiptContext context = new SttgReceiptContext();
SttgReceiptFactory.init();
// 策略模式最主要的工作: 将策略的 定义, 创建, 使用这三部分进行了解耦.
for (Receipt receipt : receiptList) {
// 获取策略
ISttgReceipt strategy = SttgReceiptFactory.getStrategy(receipt.getType());
// 设置策略
context.setSttgReceipt(strategy);
// 执行策略
context.handle(receipt);
}
}
2. FileStore
public interface IStrategyFileStore {
String upload(File file);
}
class SttgFileStoreLocal implements IStrategyFileStore {
@Override
public String upload(File file) {
System.out.println("本地文件存储");
return null;
}
}
class SttgFileStoreQN implements IStrategyFileStore {
@Override
public String upload(File file) {
System.out.println("七牛云文件存储");
return null;
}
}
class SttgFileStoreOSS implements IStrategyFileStore {
@Override
public String upload(File file) {
System.out.println("阿里云文件存储");
return null;
}
}
public class FileStoreFactory {
public static String LOCAL = "local";
public static String OSS = "oss";
public static String QN = "qn";
private static final Map<String, IStrategyFileStore> FILE_STORE_MAP = new HashMap<>();
static {
FILE_STORE_MAP.put(LOCAL, new SttgFileStoreLocal());
FILE_STORE_MAP.put(OSS, new SttgFileStoreOSS());
FILE_STORE_MAP.put(QN, new SttgFileStoreQN());
}
public static IStrategyFileStore getFileStoreStrategy(String type) {
IStrategyFileStore strategy = FILE_STORE_MAP.get(type);
return strategy == null ? FILE_STORE_MAP.get(LOCAL) : strategy;
}
}
/*
在JVM里,平时.class文件都是在硬盘中。用到它的时候会load到内存中。程序运行起来,好多class文件被load到内存中。在java中Class类封装了这类事务的属性和方法,这种方式叫反射
java9 后.newInstance()被废弃,用.getDeclaredConstructor().newInstance()替代
Sorter sorter1 = (Sorter) Class.forName("com.listao.dp.strategy.Sorter").newInstance();
Sorter sorter2 = (Sorter) Class.forName("com.listao.dp.strategy.Sorter").getDeclaredConstructor().newInstance();
*/
@Test
public void test6() {
String local = FileStoreFactory.LOCAL;
String upload = FileStoreFactory.getFileStoreStrategy(local)
.upload(new File(""));
}
3. compare
1. Comparable
public interface IComparable<T> {
int compareTo(T o);
}
@ToString
@Getter
@AllArgsConstructor
class Cat implements IComparable<Cat> {
private final int weight;
private final int height;
private int age = 0;
public Cat(int weight, int height) {
this.weight = weight;
this.height = height;
}
public int compareTo(Cat c) {
return Integer.compare(this.weight, c.weight);
}
}
@ToString
@Getter
class Dog implements IComparable<Dog> {
private final int food;
public Dog(int food) {
this.food = food;
}
@Override
public int compareTo(Dog d) {
return Integer.compare(this.food, d.food);
}
}
class Sorter_able {
/**
* 快排
*/
@SuppressWarnings("all")
public void sort(IComparable[] arr) {
for (int i = 0; i < arr.length - 1; i++) {
int minPos = i;
for (int j = i + 1; j < arr.length; j++) {
minPos = arr[j].compareTo(arr[minPos]) == -1 ? j : minPos;
}
swap(arr, i, minPos);
}
}
@SuppressWarnings("all")
void swap(IComparable[] arr, int i, int j) {
IComparable temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
@Test
public void test4() {
int[] a = {9, 2, 3, 5, 7, 1, 4};
// Comparable 并不是
Cat[] b = {new Cat(2, 3), new Cat(3, 5), new Cat(1, 1)};
Dog[] c = {new Dog(2), new Dog(3), new Dog(1)};
Sorter_able sorter = new Sorter_able();
sorter.sort(b);
System.out.println("cat: " + Arrays.toString(b));
sorter.sort(c);
System.out.println("dog: " + Arrays.toString(c));
}
2. Comparator
/**
* FunctionalInterface,函数式接口只可以有一个abstract方法,其他是default
* 接口里只有一个方法,@FunctionalInterface不写也可以
*/
@FunctionalInterface
public interface IComparator {
int compare(Cat o1, Cat o2);
/**
* java 1.8 前interface里是写不了方法体的。
* 向前兼容,default关键字,原来的方法实现接口,不继承default也是可以的
*/
default void m() {
System.out.println("m");
}
}
// ------------------------------------------------------------------
class ComparatorAge implements IComparator {
@Override
public int compare(Cat o1, Cat o2) {
return Integer.compare(o1.getAge(), o2.getAge());
}
}
class ComparatorHeight implements IComparator {
@Override
public int compare(Cat o1, Cat o2) {
return Integer.compare(o1.getHeight(), o2.getHeight());
}
}
class ComparatorWeight implements IComparator {
@Override
public int compare(Cat o1, Cat o2) {
return Integer.compare(o1.getWeight(), o2.getWeight());
}
}
class Sorter {
public void sort(Cat[] arr, IComparator comparator) {
for (int i = 0; i < arr.length - 1; i++) {
int minPos = i;
for (int j = i + 1; j < arr.length; j++) {
minPos = comparator.compare(arr[j], arr[minPos]) == -1 ? j : minPos;
}
swap(arr, i, minPos);
}
}
void swap(Cat[] arr, int i, int j) {
Cat temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
@Test
public void test5() {
Cat[] c = {new Cat(1, 3, 2), new Cat(2, 1, 3), new Cat(3, 2, 1)};
Sorter sorter = new Sorter();
sorter.sort(c, new ComparatorWeight());
System.out.println("cat_weight: " + Arrays.toString(c));
sorter.sort(c, new ComparatorHeight());
System.out.println("cat_height: " + Arrays.toString(c));
sorter.sort(c, new ComparatorAge());
System.out.println("cat_age: " + Arrays.toString(c));
// 匿名内部类,函数式接口FunctionalInterface
sorter.sort(c, (o1, o2) -> Integer.compare(o1.getWeight(), o2.getWeight()));
System.out.println("cat_innerClass: " + Arrays.toString(c));
}
5. 总结
1. 优点
- 策略类之间可以自由切换
- 由于策略类都实现同一个接口,所以使它们之间可以自由切换
- 易于扩展
- 增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
- 避免使用多重条件选择语句(if-else),充分体现面向对象设计思想
2. 缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类
- 策略模式将造成产生很多策略类,可以通过使用享元模式在一定程度上减少对象的数量
3. 使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中
作用:分离使用算法的逻辑、算法自身实现的逻辑。当优化算法自身实现逻辑时,就变得非常便捷
- 一方面可以采用最新的算法实现逻辑
- 另一方面可以直接弃用旧算法而采用新算法
一个类定义了多种行为,并且这些行为以
if-else
形式出现,可将每个条件分支移入各自的策略类中以代替这些条件语句在实际开发中,有许多算法可以实现某一功能。eg:查找、排序等
if-else
等条件判断语句来进行选择非常方便- 问题:当这个算法类中封装了大量判断算法时,该类的代码就会变得非常复杂,维护也会突然就变得非常困难
- 策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离
- 短期虽然不如直接写
if-else
来得效率高,长期看,维护单一的简单类耗时远远低于维护一个超大的复杂类
- 短期虽然不如直接写
不希望客户知道复杂的、与算法相关的数据结构,可使用策略模式来隐藏与算法相关的数据结构,提高算法的保密性与安全性
3. 责任链模式
- 责任链模式(Chain Of Responsibility Pattern)定义:避免将一个请求的发送者、接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止
1. 介绍
- 在责任链模式中,多个处理器(“接收对象”)依次处理同一个请求。一个请求先经过A处理,再把请求传递给B,B后再C,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责
2. 原理
- 抽象处理者角色(Handler):定义一个处理请求的接口,包含抽象处理方法和一个后继连接
- 具体处理者角色(Concrete Handler):实现抽象处理者的处理方法,判断能否处理本次请求,否则下一个
- 客户类角色(Client):创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程
3. 实现
- 责任链模式实现非常简单,每一个具体的处理类都会保存下一个处理类。当处理完成后,就会调用设置好的下一个处理类,直到最后一个处理类不再设置下一个处理类,这时处理链条全部完成
/**
* 请求封装
*/
@Data
@AllArgsConstructor
public class RequestData {
private String data;
}
/**
* 抽象的处理者类
*/
public abstract class AbsHandler {
// 后继处理者的引用
@Setter
protected AbsHandler next;
public abstract void handle(RequestData requestData);
}
public class HandlerA extends AbsHandler {
@Override
public void handle(RequestData requestData) {
System.out.println("HandlerA 执行代码逻辑! 处理: " + requestData.getData());
requestData.setData(requestData.getData().replace("A", ""));
// 判断时候继续向后调用处理器
if (this.next != null) {
this.next.handle(requestData);
} else {
System.out.println("执行中止");
}
}
}
public class HandlerB extends AbsHandler {
@Override
public void handle(RequestData requestData) {
System.out.println("HandlerB 执行代码逻辑! 处理: " + requestData.getData());
requestData.setData(requestData.getData().replace("B", ""));
// 判断时候继续向后调用处理器
if (this.next != null) {
this.next.handle(requestData);
} else {
System.out.println("执行中止");
}
}
}
public class HandlerC extends AbsHandler {
@Override
public void handle(RequestData requestData) {
System.out.println("HandlerC 执行代码逻辑! 处理: " + requestData.getData());
requestData.setData(requestData.getData().replace("C", ""));
// 判断时候继续向后调用处理器
if (this.next != null) {
this.next.handle(requestData);
} else {
System.out.println("执行中止");
}
}
}
@Test
public void test1() {
AbsHandler h1 = new HandlerA();
AbsHandler h2 = new HandlerB();
AbsHandler h3 = new HandlerC();
// 创建处理链
h1.setNext(h2);
h2.setNext(h3);
RequestData requestData = new RequestData("请求数据: ABCD");
// 调用处理链头部的方法
h1.handle(requestData);
}
4. 应用实例
1. 审批流程
- 模拟双11期间,业务系统审批的流程,临近双十一公司会有陆续有一些新的需求上线,为了保证线上系统的稳定,对上线的审批流畅做了严格的控制。审批的过程会有不同级别的负责人,加入进行审批(平常系统上线只需三级负责人审批即可,双十一前后需要二级或一级审核人参与审批)
1. 常规写法
/**
* 封装审核信息
*/
@Data
public class AuthInfo {
// 状态码
private String code;
// 审核相关信息
private String info = "";
public AuthInfo(String code, String... infos) {
this.code = code;
for (String str : infos) {
this.info = info.concat(str + " ");
}
}
}
/**
* 模拟审核服务
*/
public class AuthService {
// 审核信息容器 (key: 审批人Id + 审批单Id, value: 审批时间)
private static final Map<String, Date> authMap = new HashMap<>();
/**
* 审核方法
*
* @param uId 审核人id
* @param orderId 审核单id
*/
public static void auth(String uId, String orderId) {
System.out.println("进入审批流程, 审批人ID: " + uId);
authMap.put(uId.concat(orderId), new Date());
}
/**
* 查询审核结果
*/
public static Date queryAuthInfo(String uId, String orderId) {
return authMap.get(uId.concat(orderId));
}
}
/**
* 审核申请接口
*/
public class AuthController {
/**
* 审核方法
*
* @param name 申请人姓名
* @param orderId 申请单ID
* @param authDate 申请时间
*/
public AuthInfo doAuth(String name, String orderId, Date authDate) throws ParseException {
// 三级审批
Date date;
// 查询是否存在审核信息, 虚拟三级审核人ID: 1000013
date = AuthService.queryAuthInfo("1000013", orderId);
if (date == null) {
return new AuthInfo("0001", "单号: ", orderId, "状态: 等待三级审批负责人进行审批");
}
/*
* 二级审批
* - 查询是否存在审核信息, 虚拟二级审核人ID: 1000012
* - 二级审核人审核申请单的时间范围为: 11-01 ~ 11-10
*/
if (DateUtil.isIn(authDate, DateUtil.parse("2022-10-01"), DateUtil.parse("2022-10-31"))) {
// 条件成立, 查询二级审核信息
date = AuthService.queryAuthInfo("1000012", orderId);
if (date == null) {
return new AuthInfo("0001", "单号: ", orderId, "状态: 等待二级审批负责人进行审批");
}
}
/*
* 一级审批
* - 查询是否存在审核信息, 虚拟一级审核人ID: 1000011
* - 二级审核人审核申请单的时间范围为: 11-11 ~ 11-31
*/
if (DateUtil.isIn(authDate, DateUtil.parse("2022-10-01"), DateUtil.parse("2022-11-31"))) {
// 条件成立,查询二级审核信息
date = AuthService.queryAuthInfo("1000011", orderId);
if (date == null) {
return new AuthInfo("0001", "单号: ", orderId, "状态: 等待一级审批负责人进行审批");
}
}
return new AuthInfo("0001", "单号: ", orderId, "申请人: " + name, " 状态:审批完成!");
}
}
@Test
public void test2() throws ParseException {
AuthController ctl = new AuthController();
DateTime date = DateUtil.parse("2022-10-06");
// 模拟审核请求及审批操作
AuthInfo info1 = ctl.doAuth("研发小周", "100001000010000", date);
System.out.println("info1 = " + info1);
AuthService.auth("1000013", "100001000010000");
System.out.println("三级审批负责人审批完成, 审批人: 王工");
System.out.println("=================================================");
AuthInfo info2 = ctl.doAuth("研发小周", "100001000010000", date);
System.out.println("info2 = " + info2);
AuthService.auth("1000012", "100001000010000");
System.out.println("二级审批负责人审批完成, 审批人: 周经理");
System.out.println("=================================================");
AuthInfo info3 = ctl.doAuth("研发小周", "100001000010000", date);
System.out.println("info3 = " + info3);
AuthService.auth("1000011", "100001000010000");
System.out.println("一级审批负责人审批完成, 审批人: 罗总");
System.out.println("=================================================");
AuthInfo info4 = ctl.doAuth("研发小周", "100001000010000", date);
System.out.println("info4 = " + info4);
}
2. 责任链模重构
/**
* 抽象责任链类
*/
public abstract class AbsAuth {
// 审核人id
protected String userId;
// 审核人姓名
protected String userName;
// 下一个处理器对象的引用
@Getter
protected AbsAuth next;
public AbsAuth(String userId, String userName) {
this.userId = userId;
this.userName = userName;
}
// 向责任链中添加处理器
public AbsAuth appendNext(AbsAuth next) {
this.next = next;
return this;
}
// 抽象审核方法
public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}
public class AuthLevel3 extends AbsAuth {
public AuthLevel3(String userId, String userName) {
super(userId, userName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(userId, orderId);
if (date == null) {
return new AuthInfo("0001", "单号: ", orderId, " 状态: 待三级审核人审批", userName);
}
AbsAuth next = super.getNext();
if (next == null) {
return new AuthInfo("no next", "单号: ", orderId, " 状态: 三级审批完成", "审批人: ", userName);
}
return next.doAuth(uId, orderId, authDate);
}
}
public class AuthLevel2 extends AbsAuth {
private final Date beginDate = DateUtil.parse("2022-10-01 00:00:00");
private final Date endDate = DateUtil.parse("2022-10-31 00:00:00");
public AuthLevel2(String levelUserId, String levelUserName) {
super(levelUserId, levelUserName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
if (DateUtil.isIn(authDate, beginDate, endDate)) {
Date date = AuthService.queryAuthInfo(userId, orderId);
if (date == null) {
return new AuthInfo("0001", "单号: ", orderId, " 状态: 待二级审核人审批", userName);
}
AbsAuth next = super.getNext();
if (next == null) {
return new AuthInfo("no next", "单号: ", orderId, " 状态: 二级审批完成", "审批人: ", userName);
}
return next.doAuth(uId, orderId, authDate);
}
return new AuthInfo("超时,二级审批", "单号: ", orderId, "审批人: ", userName);
}
}
public class AuthLevel1 extends AbsAuth {
private final Date beginDate = DateUtil.parse("2022-10-01 00:00:00");
private final Date endDate = DateUtil.parse("2022-11-31 00:00:00");
public AuthLevel1(String userId, String userName) {
super(userId, userName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
if (DateUtil.isIn(authDate, beginDate, endDate)) {
Date date = AuthService.queryAuthInfo(userId, orderId);
if (date == null) {
return new AuthInfo("0001", "单号: ", orderId, " 状态: 待一级审核人审批", userName);
}
AbsAuth next = super.getNext();
if (next == null) {
return new AuthInfo("0001", "单号: ", orderId, " 状态: 一级审批完成", "审批人: ", userName);
}
return next.doAuth(uId, orderId, authDate);
}
return new AuthInfo("超时,一级审批", "单号: ", orderId, "审批人: ", userName);
}
}
@Test
public void test3() {
// 定义责任链
AbsAuth authLink = new AuthLevel3("1000013", "李工")
.appendNext(new AuthLevel2("1000012", "王经理")
.appendNext(new AuthLevel1("1000011", "罗总")));
Date currDate = DateUtil.parse("2022-10-18 23:49:46");
log.error(JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currDate)));
// 模拟三级负责人审批
log.info("模拟三级负责人审批,王工");
AuthService.auth("1000013", "1000998004813441");
log.error(JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currDate)));
// 模拟二级负责人审批
log.info("模拟二级负责人审批,张经理");
AuthService.auth("1000012", "1000998004813441");
log.error(JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currDate)));
// 模拟一级负责人审批
log.info("模拟一级负责人审批,段总");
AuthService.auth("1000011", "1000998004813441");
log.error(JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currDate)));
}
- 责任链已经生效,按照责任链的结构一层一层审批.当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。并且每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
2. CheckHandler
public abstract class AbsCheckHandler {
protected AbsCheckHandler next;
public abstract boolean check(String loginName, String password);
public void setNext(AbsCheckHandler next) {
this.next = next;
}
protected void nextHandler(String loginName, String password) {
if (this.next != null) {
this.next.check(loginName, password);
} else {
// todo
System.out.println("处理具体业务: ~ _ ~");
}
}
}
public class UserExistsCheckHandler extends AbsCheckHandler {
@Override
public boolean check(String loginName, String password) {
System.out.println("UserExistsCheckHandler: before");
this.nextHandler(loginName, password);
System.out.println("UserExistsCheckHandler: after");
return true;
}
}
public class UserPwdCheckHandler extends AbsCheckHandler {
@Override
public boolean check(String loginName, String password) {
System.out.println("UserPwdCheckHandler: before");
this.nextHandler(loginName, password);
System.out.println("UserPwdCheckHandler: after");
return true;
}
}
public class UserStatusCheckHandler extends AbsCheckHandler {
@Override
public boolean check(String loginName, String password) {
System.out.println("UserStatusCheckHandler: before");
this.nextHandler(loginName, password);
System.out.println("UserStatusCheckHandler: after");
return true;
}
}
@Test
public void fang() {
AbsCheckHandler existsHandler = new UserExistsCheckHandler();
AbsCheckHandler userStatusHandler = new UserStatusCheckHandler();
AbsCheckHandler pwdCheckHandler = new UserPwdCheckHandler();
existsHandler.setNext(pwdCheckHandler);
pwdCheckHandler.setNext(userStatusHandler);
boolean flag = existsHandler.check("admin", "123456");
}
3. list_chain
/**
* chain没有实现Filter,两链不可以串连
*/
class Chain {
List<IFilter> filters = new ArrayList<>();
int index = 0;
public Chain add(IFilter f) {
filters.add(f);
return this;
}
/**
* 这个方法的名称可以任意
*/
public void execute() {
if (index == filters.size()) {
// todo 处理业务逻辑
System.out.println("处理业务逻辑: ~ _ ~");
return;
}
IFilter f = filters.get(index);
index++;
f.doFilter(this);
}
}
interface IFilter {
void doFilter(Chain chain);
}
class FilterHTML implements IFilter {
@Override
public void doFilter(Chain chain) {
System.out.println("HTMLFilter: before");
chain.execute();
System.out.println("HTMLFilter: after");
}
}
class FilterSensitive implements IFilter {
@Override
public void doFilter(Chain chain) {
System.out.println("SensitiveFilter: before");
chain.execute();
System.out.println("SensitiveFilter: after");
}
}
/**
* 下面几种官方都是这种方式
* 1. servlet过滤器filter
* 2. struct2拦截器interceptor
* 3. spring mvc拦截器interceptor
*/
public static void main(String[] args) {
Chain c1 = new Chain()
.add(new FilterHTML())
.add(new FilterSensitive());
c1.execute();
}
1. chain + chain
/**
* 1. 在Chain中加入node的index信息, 每次chain被invoke切到下一个node
*/
class Chain implements IFilter {
List<IFilter> filters = new ArrayList<>();
int index = 0;
public Chain add(IFilter f) {
filters.add(f);
return this;
}
public void doFilter(IFilter chain) {
if (index == filters.size()) {
// todo 处理业务逻辑
System.out.println("处理业务逻辑: ~ _ ~");
return;
}
IFilter f = filters.get(index);
index++;
f.doFilter(this);
}
}
interface IFilter {
/**
* 2. doFilter增加形参Chain, 使得node可以调用Chain
*/
void doFilter(IFilter chain);
}
class FilterHTML implements IFilter {
@Override
public void doFilter(IFilter chain) {
System.out.println("HTMLFilter: before");
chain.doFilter(null);
System.out.println("HTMLFilter: after");
}
}
class FilterSensitive implements IFilter {
@Override
public void doFilter(IFilter chain) {
System.out.println("SensitiveFilter: before");
chain.doFilter(null);
System.out.println("SensitiveFilter: after");
}
}
class URLFilter implements IFilter {
@Override
public void doFilter(IFilter chain) {
System.out.println("URLFilter: before");
chain.doFilter(null);
System.out.println("URLFilter: after");
}
}
class FilterFace implements IFilter {
@Override
public void doFilter(IFilter chain) {
System.out.println("FaceFilter: before");
chain.doFilter(null);
System.out.println("FaceFilter: after");
}
}
public static void main(String[] args) {
Chain c1 = new Chain()
.add(new FilterHTML())
.add(new FilterSensitive());
Chain c2 = new Chain()
.add(new FilterFace())
.add(new URLFilter());
c1.add(c2); // chain实现Filter, 两链可以串连
c1.doFilter(null);
}
5. 总结
1. 优点
- 降低对象间(请求发送者、接收者)耦合度。一个节点对象只需保持一个指向后节点的引用,避免了众多
if-else
- 提高可扩展性
- 开闭原则。增强了给对象指派职责的灵活性。工作流程发生变化,动态地改变、新增、删除节点
- 单一职责原则。每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围
2. 缺点
- 不能保证每个请求一定被处理。无节点处理
- 系统性能将受到一定影响。对比较长的责任链,请求的处理可能涉及多个处理对象
- 造成循环调用。责任链建立的合理性要靠Client来保证,增加了客户端的复杂性,由于责任链错误设置而导致系统出错
3. 使用场景
- 在运行时需要动态使用多个关联对象来处理同一次请求。eg:请假流程、员工入职流程、编译打包发布上线流程等
- 不想让使用者知道具体的处理逻辑。eg:做权限校验的登录拦截器
- 需要动态更换处理对象。eg:工单处理系统、网关API过滤规则系统等
- 常被用在框架开发,实现框架过滤器、拦截器功能,让框架的使用者在不修改源码的情况下,添加新的过滤拦截功能
4. 观察者模式
- 观察者模式(Observer Pattern)原始定义:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新
- 观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(Model-View)模式、源-监听(Source-Listener)模式等
- 在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加、删除观察者,使得系统更易于扩展
1. 介绍
- 应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路,都有其影子
- 基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身操作处理
- 生活中也有许多观察者模式的应用
- eg:汽车与红绿灯的关系
2. 原理
- 观察者模式结构中通常包括:观察目标、观察者两个继承层次结构
Subject
:抽象被观察者。抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加、删除观察者对象ConcreteSubject
:具体被观察者。该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知Observer
:抽象观察者。定义了一个更新接口,使得在得到主题更改通知时更新自己ConcreteObserver
:具体观察者。以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致
3. 实现
/**
* 抽象观察者
*/
public interface Observer {
// update方法: 为不同的观察者更新行为定义一个相同的接口, 不同的观察者对该接口有不同的实现
void update();
}
/**
* 具体的观察者
*/
public class ConcreteObserver1 implements Observer {
@Override
public void update() {
System.out.println("ConcreteObserver1 得到通知, 更新状态! !");
}
}
public class ConcreteObserver2 implements Observer {
@Override
public void update() {
System.out.println("ConcreteObserver2 得到通知,更新状态! !");
}
}
/**
* 抽象目标类
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
/**
* 具体目标类
*/
public class ConcreteSubject implements Subject {
// 定义集合, 存储所有的观察者对象
private ArrayList<Observer> observers = new ArrayList<>();
// 注册方法, 向观察者集合增加一个观察者
@Override
public void attach(Observer observer) {
observers.add(observer);
}
// 注销方法, 用于从观察者集合中移除一个观察者
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
// 通知方法
@Override
public void notifyObservers() {
// 遍历集合, 调用每一个观察者的响应方法
for (Observer observer : observers) {
observer.update();
}
}
}
@Test
public void test1() {
// 创建目标类
Subject subject = new ConcreteSubject();
// 注册观察者, 注册多个
subject.attach(new ConcreteObserver1());
subject.attach(new ConcreteObserver2());
// 具体的主题内部发生改变, 给所有注册的观察者发送通知
subject.notifyObservers();
}
4. 应用实例
1. 买房摇号
- 实现一个买房摇号的程序。摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息
1. 常规写法
/**
* 模拟买房摇号服务
*/
public class DrawHouseService {
// 摇号抽签
public String lots(String uId) {
if (uId.hashCode() % 2 == 0) {
return "恭喜ID为: " + uId + " 的用户, 在本次摇号中中签!";
} else {
return "很遗憾, ID为: " + uId + "的用户, 您本次未中签!";
}
}
}
/**
* 开奖结果封装类
*/
@Data
@AllArgsConstructor
public class LotteryRst {
private String uId; // 用户id
private String msg; // 摇号信息
private Date dateTime; // 业务时间
}
/**
* 开奖服务接口
*/
public interface LotteryService {
// 开奖之后的业务操作
LotteryRst lottery(String uId);
}
public class LotteryServiceImpl implements LotteryService {
// 注入摇号服务
private DrawHouseService houseService = new DrawHouseService();
@Override
public LotteryRst lottery(String uId) {
// 1. 摇号
String result = houseService.lots(uId);
// 2. 发短信
System.out.println("发送短信通知用户, ID为: " + uId + ", 您的摇号结果如下: " + result);
// 3. 发送MQ信息
System.out.println("记录用户摇号结果到MQ, ID为: " + uId + ", 摇号结果: " + result);
return new LotteryRst(uId, result, new Date());
}
}
@Test
public void test2() {
LotteryService lotteryService = new LotteryServiceImpl();
LotteryRst result = lotteryService.lottery("13579246810101010");
System.out.println(result);
}
2. 观察者重构
- 上面摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但除了摇号这个核心功能外,发短信、记录信息MQ都不是主链路功能,单独抽取出来,保证可扩展性、可维护性
/**
* 事件监听接口
*/
public interface IListener {
void doEvent(LotteryRst result);
}
/**
* MQ消息发送事件监听
*/
public class ListenerMQ implements IListener {
@Override
public void doEvent(LotteryRst result) {
System.out.println("记录用户的摇号结果(MQ), 用户ID: " + result.getUId() + ", 摇号结果: " + result.getMsg());
}
}
/**
* 短信发送事件监听类
*/
public class ListenerMsg implements IListener {
@Override
public void doEvent(LotteryRst result) {
System.out.println("发送短信通知, 用户ID: " + result.getUId() + ", 您的摇号结果为: " + result.getMsg());
}
}
public enum EventType {
MQ, Msg
}
public interface IEventManager {
void subscribe(Enum<EventType> eventType, IListener listener);
void unsubscribe(Enum<EventType> eventType, IListener listener);
void notify(Enum<EventType> eventType, LotteryRst result);
}
/**
* 事件处理类
*/
public class EventManager implements IEventManager {
// 监听器集合
Map<Enum<EventType>, List<IListener>> listeners = new HashMap<>();
@SafeVarargs
public EventManager(Enum<EventType>... operations) {
for (Enum<EventType> operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
/**
* 订阅
*
* @param eventType 事件类型
* @param listener 监听对象
*/
public void subscribe(Enum<EventType> eventType, IListener listener) {
List<IListener> list = listeners.get(eventType);
list.add(listener);
}
/**
* 取消订阅
*
* @param eventType 事件类型
* @param listener 监听对象
*/
public void unsubscribe(Enum<EventType> eventType, IListener listener) {
List<IListener> list = listeners.get(eventType);
list.remove(listener);
}
/**
* 通知
*/
public void notify(Enum<EventType> eventType, LotteryRst result) {
List<IListener> list = listeners.get(eventType);
for (IListener listener : list) {
listener.doEvent(result);
}
}
}
- 摇号业务处理
/**
* 开奖服务接口
*/
public abstract class AbsLottery {
private final IEventManager iEventManager;
public AbsLottery() {
// 设置事件的类型
iEventManager = new EventManager(EventType.MQ, EventType.Msg);
// 订阅
iEventManager.subscribe(EventType.Msg, new ListenerMsg());
iEventManager.subscribe(EventType.MQ, new ListenerMQ());
}
public abstract LotteryRst lottery(String uId);
// template method
public LotteryRst lotteryAndMsg(String uId) {
LotteryRst lottery = lottery(uId);
// 发送通知
iEventManager.notify(EventType.Msg, lottery);
iEventManager.notify(EventType.MQ, lottery);
return lottery;
}
}
public class LotteryImpl extends LotteryAbs {
// 注入摇号服务
private final DrawHouseService houseService = new DrawHouseService();
@Override
public LotteryRst lottery(String uId) {
// 1.摇号
String result = houseService.lots(uId);
return new LotteryRst(uId, result, new Date());
}
}
@Test
public void test3() {
AbsLottery ls = new LotteryImpl();
LotteryRst lotteryResult = ls.lotteryAndMsg("215673512673512736125763");
System.out.println(lotteryResult);
}
2. Child
1. 观察者接口
/**
* 1. 增加观察者接口, 分离观察者与被观察者(内嵌责任链)
*/
class Child {
boolean cry = false;
List<Observer> observers = new ArrayList<>();
// 语句块,也可以把类名写到配置文件里加入
{
observers.add(new Observer_Dad());
observers.add(new Observer_Mum());
observers.add(new Observer_Dog());
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
cry = true;
// 用observe_interface分离 观察者、被观察者
for (Observer o : observers) {
o.actionOnWakeUp();
}
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
// do sth
c.wakeUp();
}
}
// ------------------------------------------------------------------------
/**
* 新增接口
*/
interface Observer {
void actionOnWakeUp();
}
class Observer_Dad implements Observer {
public void feed() {
System.out.println("dad feeding...");
}
@Override
public void actionOnWakeUp() {
feed();
}
}
class Observer_Mum implements Observer {
public void hug() {
System.out.println("mum hugging...");
}
@Override
public void actionOnWakeUp() {
hug();
}
}
class Observer_Dog implements Observer {
public void wang() {
System.out.println("dog wang...");
}
@Override
public void actionOnWakeUp() {
wang();
}
}
2. 增加Event
/**
* 有很多时候, 观察者需要根据事件的具体情况来进行处理
* 1. 新增事件类
* 2. 触发事件fireEvent
*/
class Child {
private boolean cry = false;
private final List<Observer> observers = new ArrayList<>();
{
observers.add(new Observer_Dad());
observers.add(new Observer_Mum());
observers.add(new Observer_Dog());
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
cry = true;
WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(), "bed");
for (Observer o : observers) {
o.actionOnWakeUp(event);
}
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
// do sth
c.wakeUp();
}
}
// ------------------------------------------------------------------------
interface Observer {
void actionOnWakeUp(WakeUpEvent event);
}
class Observer_Dad implements Observer {
public void feed() {
System.out.println("dad feeding...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
feed();
}
}
class Observer_Dog implements Observer {
public void wang() {
System.out.println("dog wang...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
wang();
}
}
class Observer_Mum implements Observer {
public void hug() {
System.out.println("mum hugging...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
hug();
}
}
// ------------------------------------------------------------------------
/**
* 事件类
*/
@AllArgsConstructor
class WakeUpEvent {
long timestamp;
String loc;
}
3. 增加Event源
/**
* 1. 处理事件的时候, 需要事件源对象
* WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(), "bed", this);
*/
class Child {
private boolean cry = false;
private final List<Observer> observers = new ArrayList<>();
{
observers.add(new Observer_Dad());
observers.add(new Observer_Mum());
observers.add(new Observer_Dog());
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
cry = true;
// 增加事件源参数
WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(), "bed", this);
for (Observer o : observers) {
o.actionOnWakeUp(event);
}
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
//do sth
c.wakeUp();
}
}
// ------------------------------------------------------------------------
interface Observer {
void actionOnWakeUp(WakeUpEvent event);
}
class Observer_Dad implements Observer {
public void feed() {
System.out.println("dad feeding...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
feed();
}
}
class Observer_Dog implements Observer {
public void wang() {
System.out.println("dog wang...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
wang();
}
}
class Observer_Mum implements Observer {
public void hug() {
System.out.println("mum hugging...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
hug();
}
}
// ------------------------------------------------------------------------
/**
* 增加事件源对象
*/
@AllArgsConstructor
class WakeUpEvent {
long timestamp;
String loc;
Child source;
}
4. 新增AbsEvent
/**
* 1. 新增abstractClass Event<T>
* 2. 事件也可以形成继承体系
*/
class Child {
boolean cry = false;
List<Observer> observers = new ArrayList<>();
{
observers.add(new Observer_Dad());
observers.add(new Observer_Mum());
observers.add(new Observer_Dog());
observers.add((e) -> {
System.out.println("ppp");
});
// hook js中的钩子函数。callback function
}
public boolean isCry() {
return cry;
}
public void wakeUp() {
cry = true;
WakeUpEvent event = new WakeUpEvent(System.currentTimeMillis(), "bed", this);
for (Observer o : observers) {
o.actionOnWakeUp(event);
}
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
//do sth
c.wakeUp();
}
}
// ------------------------------------------------------------------------
interface Observer {
void actionOnWakeUp(WakeUpEvent event);
}
class Observer_Dad implements Observer {
public void feed() {
System.out.println("dad feeding...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
feed();
System.out.println(event.getSource());
}
}
class Observer_Mum implements Observer {
public void hug() {
System.out.println("mum hugging...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
hug();
}
}
class Observer_Dog implements Observer {
public void wang() {
System.out.println("dog wang...");
}
@Override
public void actionOnWakeUp(WakeUpEvent event) {
wang();
}
}
// ------------------------------------------------------------------------
/**
* 新增event抽象类
*/
abstract class Event<T> {
abstract T getSource();
}
@AllArgsConstructor
class WakeUpEvent extends Event<Child> {
long timestamp;
String loc;
Child source;
@Override
Child getSource() {
return source;
}
}
3. Frame
import java.awt.Button;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
/**
* AWT java图形界面
*/
public class T_Frame extends Frame {
public void launch() {
// 事件源对象. 创建button, 并为其中观察者
Button b = new Button("press me");
b.addActionListener(new Observer_01());
b.addActionListener(new Observer_02());
// button加到窗口
this.add(b);
// button多大窗口多大
this.pack();
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
this.setLocation(400, 400);
this.setVisible(true);
}
// ------------------------------------------------------
public static void main(String[] args) {
new T_Frame().launch();
}
/**
* Observer_01
*/
static class Observer_01 implements ActionListener {
public void actionPerformed(ActionEvent e) {
((Button) e.getSource()).setLabel("press me again!");
System.out.println("button pressed!");
}
}
/**
* Observer_02
*/
static class Observer_02 implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("button pressed 2!");
}
}
}
5. 总结
1. 优点
- 降低了目标、观察者之间的耦合关系,两者之间是抽象耦合关系
- 被观察者发送通知,所有注册的观察者都会收到信息【实现广播机制】
2. 缺点
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,导致系统崩溃
3. 使用场景
- 当一个对象状态改变需要改变其他对象时
- eg:商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量
- 一个对象发生改变时只想要发送通知,而不需要知道接收者是谁
- eg:订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号
- 需要创建一种链式触发机制时
- eg:在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象...通过观察者模式能够很好地实现
- 微博、微信朋友圈发送的场景
- eg:一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知
- 需要建立基于事件触发的场景
- eg:基于 Java UI 的编程,所有键盘、鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它
4. JDK支持
可以直接使用Observer
接口和Observable
类作为观察者模式的抽象层,自定义具体观察者类、具体观察目标类
java.util.Observer
接口: 抽象观察者
package java.util;
public interface Observer {
void update(Observable o, Object arg);
}
java.util.Observable
类: 被观察者,在该类中定义了一个Vector
集合来存储观察者对象
package java.util;
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
// 将新的观察者对象添加到集合中
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
public void notifyObservers() {
notifyObservers(null);
}
// 调用所有观察者对象的`update()`,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知
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;
}
}
5. 状态模式
- 状态模式(State Pattern)定义:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类
1. 介绍
- 自然界很多事物都有多种状态,而且不同状态下具有不同的行为,这些状态在特定条件下还会发生相互转换。eg:水
- 软件系统中,有些对象也像水一样具有多种状态,这些对象在不同状态下也具有不同的行为,在某些情况下能够相互转换
- 状态模式 解决系统中复杂对象的状态转换及不同状态下行为的封装问题
- 将一个对象的状态从该对象中分离出来,封装到专门的状态类中(用类来表示状态),使得对象状态可以灵活变化
2. 结构
- 上下文信息类(Context):实际上就是存储当前状态的类,对外提供更新状态的操作。在该类中维护着一个抽象状态接口State实例,这个实例定义当前状态
- 抽象状态类(State):可以是一个接口或抽象类,用于定义声明状态更新的操作方法有哪些,具体实现由子类完成
- 具体状态类(StateA):实现抽象状态类定义的方法,根据具体的场景来指定对应状态改变后的代码实现逻辑
3. 实现
/**
* 抽象状态接口
*/
public interface IState {
// 声明抽象方法, 不同具体状态类可以有不同的实现
void handle(Context context);
}
public class StateA implements IState {
@Override
public void handle(Context context) {
System.out.println("进入到状态模式A...");
context.setState(this);
}
@Override
public String toString() {
return "当前状态: StateA";
}
}
public class StateB implements IState {
@Override
public void handle(Context context) {
System.out.println("进入到状态模式B...");
context.setState(this);
}
@Override
public String toString() {
return "当前状态: StateB";
}
}
/**
* 上下文类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Context {
// 维持一个对状态对象的引用
private IState state;
}
@Test
public void test1() {
Context context = new Context();
IState state1 = new StateA();
state1.handle(context);
System.out.println(context.getState().toString());
System.out.println("=================================");
IState state2 = new StateB();
state2.handle(context);
System.out.println(context.getState().toString());
}
4. 应用实例
1. TrafficLight
- 模拟交通信号灯状态转换。红、黄、绿3种颜色状态,不同状态之间的切换逻辑为
- 红灯只能切换黄灯
- 黄灯可切换绿灯、红灯
- 绿灯只能切换黄灯
1. 常规写法
- 问题: 状态切换的操作全部在一个类中,如果有很多的交通灯进行联动,这个程序的逻辑就会变得非常复杂,难以维护
/*
* 交通灯类有三种状态
* 红灯(禁行), 黄灯(警示), 绿灯(通行)
*/
public class TrafficLight {
// 初始化状态
private String state = "红色";
// 切换为绿灯, 通行状态
public void switchToGreen() {
if ("绿".equals(state)) {
System.out.println("当前为绿灯, 无需切换!");
} else if ("红".equals(state)) {
System.out.println("红灯不能切换为绿灯");
} else if ("黄".equals(state)) {
state = "绿";
System.out.println("绿灯亮起... 时长: 60秒");
}
}
// 切换为黄灯, 警示状态
public void switchToYellow() {
if ("黄".equals(state)) {
System.out.println("当前为黄灯, 无需切换!");
} else if ("红".equals(state) || "绿".equals(state)) {
System.out.println("红灯不能切换为绿灯");
state = "黄";
System.out.println("黄灯亮起... 时长: 10秒");
}
}
// 切换为红灯, 禁止状态
public void switchToRed() {
if ("红".equals(state)) {
System.out.println("当前为红灯, 无需切换!");
} else if ("绿".equals(state)) {
System.out.println("绿灯不能切换为红灯");
} else if ("黄".equals(state)) {
state = "红";
System.out.println("红灯亮起... 时长: 90秒");
}
}
}
2. 状态模式重构
- 将交通灯的切换逻辑组织起来,把跟状态有关的内容从交通灯类里抽离出来,使用类来表示不同的状态
- 将"状态"接口化、模块化,最终将它们从臃肿的交通类中抽了出来,消除了原来
TrafficLight
类中的if-else
,代码干净而优雅
/**
* 交通灯状态接口
*/
public interface IState {
// 切换为绿灯
void switchToGreen(TrafficLightContext trafficLight);
// 切换为黄灯
void switchToYellow(TrafficLightContext trafficLight);
// 切换为红灯
void switchToRed(TrafficLightContext trafficLight);
}
public class StateRed implements IState {
@Override
public void switchToGreen(TrafficLightContext trafficLight) {
System.out.println("红灯不能切换为绿灯!");
}
@Override
public void switchToYellow(TrafficLightContext trafficLight) {
System.out.println("黄灯亮起... 时长: 10秒");
trafficLight.setState(new StateYellow());
}
@Override
public void switchToRed(TrafficLightContext trafficLight) {
System.out.println("当前为红灯, 无需切换!");
}
}
public class StateYellow implements IState {
@Override
public void switchToGreen(TrafficLightContext trafficLight) {
System.out.println("绿灯亮起... 时长: 60秒!");
trafficLight.setState(new StateGreen());
}
@Override
public void switchToYellow(TrafficLightContext trafficLight) {
System.out.println("当前是黄灯, 无需切换!");
}
@Override
public void switchToRed(TrafficLightContext trafficLight) {
System.out.println("红灯亮起... 时长: 90秒!");
trafficLight.setState(new StateRed());
}
}
public class StateGreen implements IState {
@Override
public void switchToGreen(TrafficLightContext trafficLight) {
System.out.println("当前是绿灯, 无需切换!");
}
@Override
public void switchToYellow(TrafficLightContext trafficLight) {
System.out.println("黄灯亮起... 时长: 10秒");
trafficLight.setState(new StateYellow());
}
@Override
public void switchToRed(TrafficLightContext trafficLight) {
System.out.println("绿灯不能够切换为红灯!");
}
}
/**
* 交通灯类
*/
@Getter
@Setter
@ToString
public class TrafficLightContext {
// 初始化-红灯
private IState state;
// 切换为绿灯, 通行状态
public void switchToGreen() {
state.switchToGreen(this);
}
// 切换为黄灯, 警示状态
public void switchToYellow() {
state.switchToYellow(this);
}
// 切换为红灯, 禁止状态
public void switchToRed() {
state.switchToRed(this);
}
}
@Test
public void test2() {
TrafficLightContext light = new TrafficLightContext();
light.setState(new StateRed()); // 当前状态
light.switchToRed();
light.switchToGreen();
light.switchToYellow();
light.switchToGreen();
System.out.println(light.getState());
}
2. ThreadState
public abstract class AbsThreadState_ {
Thread_ t;
abstract void move(Z_main input);
abstract void run();
}
@NoArgsConstructor
class NewState extends AbsThreadState_ {
public NewState(Thread_ t) {
this.t = t;
}
@Override
void move(Z_main input) {
if ("start".equals(input.msg))
t.state = new RunningState(t);
}
@Override
void run() {
}
}
class RunningState extends AbsThreadState_ {
public RunningState(Thread_ t) {
this.t = t;
}
@Override
void move(Z_main input) {
}
@Override
void run() {
}
}
class TerminatedState extends AbsThreadState_ {
public TerminatedState(Thread_ t) {
this.t = t;
}
@Override
void move(Z_main input) {
}
@Override
void run() {
}
}
public class Thread_ {
AbsThreadState_ state;
public Thread_() {
state = new NewState();
}
void move(Z_main input) {
state.move(input);
}
void run() {
state.run();
}
}
public class Z_main {
String msg;
public static void main(String[] args) {
Thread_ thread_ = new Thread_();
}
}
3. MoodState
public abstract class AbsMoodState {
abstract void smile();
abstract void cry();
abstract void say();
}
class SadState extends AbsMoodState {
@Override
void smile() {
}
@Override
void cry() {
}
@Override
void say() {
}
}
class NervousState extends AbsMoodState {
@Override
void smile() {
}
@Override
void cry() {
}
@Override
void say() {
}
}
class HappyState extends AbsMoodState {
@Override
void smile() {
System.out.println("happy smile");
}
@Override
void cry() {
}
@Override
void say() {
}
}
/**
* 当增加新的状态时非常不方便
* 类里的方法不再进行扩展,否则所有状态的state都要改变
*/
public class Z_MM {
String name;
AbsMoodState state;
public Z_MM(AbsMoodState state) {
this.state = state;
}
public void smile() {
state.smile();
}
public void cry() {
state.cry();
}
public void say() {
state.say();
}
public static void main(String[] args) {
Z_MM mm = new Z_MM(new HappyState());
mm.smile();
}
}
5. 总结
1. 优点
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为
- 状态转换逻辑、状态对象合成一体,而不是某一个巨大的条件语句块
2. 缺点
- 增加系统类和对象的个数
- 结构与实现较为复杂,使用不当将导致程序结构和代码混乱
- 对《开闭原则》的支持并不太好(添加新的状态类需要修改Context源代码)
3. 使用场景
- 对象状态变化进行不同行为操作时。eg:购物订单状态
- 对象根据自身变量的当前值改变行为,不期望使用大量
if-else
。eg:商品库存状态 - 对于某些确定的状态和行为,不想使用重复代码时。eg:某一个会员当天的购物浏览记录
6. 访问者模式
- 访问者模式(Visitor Pattern)原始定义:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离
- 运行时使用一组对象的一个或多个操作
- eg:对不同类型的文件(
.pdf, .xml, .properties
)进行扫描
- eg:对不同类型的文件(
- 分离对象本身结构和对象的操作
- eg:扫描多个文件夹下的多个文件,对于文件来说,扫描是额外的业务操作,如果在每个文件对象上都加一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式
- 运行时使用一组对象的一个或多个操作
- 访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法让多样化的算法自成体系,多态化的访问者接口保证了系统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据
1. 介绍
- 实际开发中使用很少,因为比较难以实现并且可读性、可维护性变差
- 主要解决:数据与算法耦合问题,尤其在数据结构比较稳定,而算法多变的情况下。为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展
2. 原理
- 抽象访问者角色(Visitor):接口或抽象类,定义了一系列操作方法,用来处理所有数据元素,通常为同名的访问方法,并以数据元素类作为入参来确定那个重载方法被调用
- 具体访问者角色(ConcreteVisitor):访问者接口的实现类,可以有多个实现,每个访问者都需要实现所有数据元素类型的访问重载方法
- 抽象元素角色(Element):被访问的数据元素接口,定义了一个接受访问者的方法(
accept
),其意义是指,每一个元素都要可以被访问者访问 - 具体元素角色(ConcreteElement):具体数据元素实现类,提供接受访问方法的具体实现,而这个具体的实现,通常情况下是使用访问者提供的访问该元素类的方法,其accept实现方法中调用访问者并将自己 "this" 传回
- 对象结构角色(Object Structure):包含所有可能被访问的数据对象的容器,可以提供数据对象的迭代功能,可以是任意类型的数据结构
- 客户端(Client):使用容器并初始化其中各类数据元素,并选择合适的访问者处理容器中的所有数据对象
3. 实现
- 以超市购物为例,假设超市中的三类商品: 水果、糖果、酒水进行售卖
- 忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计。我们先来定义糖果类、酒类、水果类
1. 商品类
/**
* 抽象商品父类
*/
@Data
@AllArgsConstructor
public abstract class AbsProduct {
// 商品名
private String name;
// 生产日期
private LocalDate produceDate;
// 商品价格
private double price;
}
/**
* 糖果类
*/
public class Candy extends AbsProduct implements IAcceptable {
public Candy(String name, LocalDate produceDate, double price) {
super(name, produceDate, price);
}
@Override
public void accept(Visitor visitor) {
// 在accept方法中调用访问者, 并将自己this传递回去
visitor.visit(this);
}
}
/**
* 水果类
*/
@Getter
@Setter
public class Fruit extends AbsProduct implements IAcceptable {
// 重量
private double weight;
public Fruit(String name, LocalDate produceDate, double price, double weight) {
super(name, produceDate, price);
this.weight = weight;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 酒水类
*/
public class Wine extends AbsProduct implements IAcceptable {
public Wine(String name, LocalDate produceDate, double price) {
super(name, produceDate, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
2. 访问者
- 访问者接口(各种重载)
- 收银员就类似于访问者,访问用户选择的商品,采用不同计价方法。假设根据生产日期进行打折,过期商品不能够出售,注意这种计价策略不适用于酒类
/**
* 访问者接口 - 根据入参的不同调用对应的重载方法
*/
public interface Visitor {
// 糖果重载方法
void visit(Candy candy);
// 酒类重载方法
void visit(Wine wine);
// 水果重载方法
void visit(Fruit fruit);
}
- 具体访问者
- 创建计价业务类,
visit()
多态性,对三类商品进行折扣计价
/**
* 折扣计价访问者类
*/
public class DiscountVisitor implements Visitor {
private final LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
this.billDate = billDate;
System.out.println("结算日期: " + billDate);
}
@Override
public void visit(Candy candy) {
System.out.println("糖果: " + candy.getName());
long days = billDate.toEpochDay() - candy.getProduceDate().toEpochDay();
// 糖果大于180天, 禁止售卖, 否则糖果一律九折
if (days > 180) {
System.out.println("超过半年的糖果, 请勿食用!");
} else {
double realPrice = candy.getPrice() * 0.9;
System.out.println("糖果打折后的价格为: " + NumberFormat.getCurrencyInstance().format(realPrice));
}
}
@Override
public void visit(Wine wine) {
System.out.println("酒类: " + wine.getName() + ", 无折扣价格!");
System.out.println("原价售卖: " + NumberFormat.getCurrencyInstance().format(wine.getPrice()));
}
@Override
public void visit(Fruit fruit) {
System.out.println("水果: " + fruit.getName());
long days = billDate.toEpochDay() - fruit.getProduceDate().toEpochDay();
double rate = 0;
// 3天后5折,7天后不卖
if (days > 7) {
System.out.println("超过七天的水果, 请勿食用!");
} else if (days > 3) {
rate = 0.5;
} else {
rate = 1;
}
double realPrice = fruit.getPrice() * fruit.getWeight() * rate;
System.out.println("水果价格为: " + NumberFormat.getCurrencyInstance().format(realPrice));
}
}
3. 客户端
@Test
public void test1() {
Candy candy = new Candy("德芙巧克力", LocalDate.of(2022, 1, 1), 10.0);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022, 10, 5));
visitor.visit(candy);
}
- 访问者的重载方法只能对单个具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题
- 接待访问者的类
Acceptable
,定义了一个accept(Visitor visitor)
,只要是Visitor
的子类都可以接收
@Test
public void test2() {
// 将3件商品加入购物车
List<AbsProduct> products = Arrays.asList(
new Candy("金丝猴奶糖", LocalDate.of(2022, 10, 1), 10),
new Wine("郎酒", LocalDate.of(2022, 10, 1), 1000),
new Fruit("草莓", LocalDate.of(2022, 10, 8), 50, 1)
);
// Visitor visitor = new DiscountVisitor(LocalDate.of(2022, 10, 5));
// for (Product product : products) {
// visitor.visit();
// }
}
/**
* 接待者这接口 (抽象元素角色)
*/
public interface IAcceptable {
// 接收所有的Visitor访问者的子类
void accept(Visitor visitor);
}
@Test
public void test3() {
// 模拟添加多个商品
List<IAcceptable> list = Arrays.asList(
new Candy("金丝猴奶糖", LocalDate.of(2022, 10, 1), 10),
new Wine("郎酒", LocalDate.of(2022, 10, 1), 1000),
new Fruit("草莓", LocalDate.of(2022, 10, 8), 50, 1)
);
Visitor visit = new DiscountVisitor(LocalDate.of(2022, 10, 11));
for (IAcceptable product : list) {
product.accept(visit);
}
}
4. 总结
1. 优点
- 扩展性好
- 在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能
- 复用性好
- 通过访问者来定义整个对象结构通用的功能,从而提高复用程度
- 分离无关行为
- 通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一
2. 缺点
- 对象结构变化很困难
- 在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,违背了“开闭原则”
- 违反了依赖倒置原则
- 访问者模式依赖了具体类,而没有依赖抽象类
3. 使用场景
- 当对象的数据结构相对稳定,而操作却经常变化时
- eg:路由器本身的内部构造(也就是数据结构)不怎么变化,但在不同操作系统下的操作(发送数据、接收数据等)可能会经常变化
- 将数据结构与不常用的操作进行分离时
- eg:扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作
- 需要在运行时动态决定使用哪些对象和方法时
- eg:对于监控系统,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,访问者模式就可以动态增加监控行为
7. 命令模式
- 命令模式(Command Pattern)定义:将请求(命令)封装为一个对象,可以使用不同的请求参数化其他对象(将不同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等 (附加控制)功能
1. 介绍
- 命令模式的核心:将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务
- C语言支持函数指针,可以把函数当作变量传递。但大部分编程语言,函数没法作为参数传递给其他函数,也没法赋值给变量
- 借助命令模式,可以将函数封装成对象。设计一个包含这个函数的类,实例化这个对象进行传递,实现函数像对象一样使用
2. 原理
- 抽象命令类角色(Command): 定义命令的接口,声明执行的方法
- 具体命令角色(Concrete Command):具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作
- 实现者 / 接收者角色(Receiver): 真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能
- 调用者 / 请求者角色(Invoker): 通常会持有命令对象(很多)。客户端真正触发命令并要求命令执行的对象,相当于使用命令对象的入口
3. 实现
模拟酒店后厨的出餐流程
- 服务员:即调用者角色,由她来发起命令
- 厨师:接收者,真正执行命令的对象
- 订单:命令中包含订单
/**
* 订单类
*/
@Data
public class Order {
// 餐桌号码
private int tableNum;
// 存储菜名、份数
private Map<String, Integer> foodMenu = new HashMap<>();
}
/**
* Receiver接收者角色 -> 厨师类
*/
public class Chef {
public void makeFood(int num, String foodName) {
System.out.println(num + "份, " + foodName);
}
}
1. Command对象
/**
* 抽象命令接口
*/
public interface ICommand {
// 统一的执行方法
void execute();
}
/**
* 命令对象
*/
public class CommandOrder implements ICommand {
// 接收者对象的引用
private final Chef chef;
private final Order order;
public CommandOrder(Chef chef, Order order) {
this.chef = chef;
this.order = order;
}
@Override
public void execute() {
System.out.println(order.getTableNum() + "桌的订单: ");
Set<String> keySet = order.getFoodMenu().keySet();
for (String key : keySet) {
chef.makeFood(order.getFoodMenu().get(key), key);
}
try {
Thread.sleep(1_000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(order.getTableNum() + "桌的菜品已经上齐!");
}
}
2. Invoker对象
/**
* Invoker调用者 -> 服务员
*/
public class Waiter {
// 可以持有多个命令对象
private final ArrayList<ICommand> commands;
public Waiter() {
this.commands = new ArrayList<>();
}
public Waiter(ArrayList<ICommand> commands) {
this.commands = commands;
}
public void setCommand(ICommand command) {
this.commands.add(command);
}
// 发出指令
public void orderUp() {
System.out.println("叮咚! 服务员: 有新的订单, 请师傅开始制作...");
for (ICommand command : commands) {
if (command != null) {
command.execute();
}
}
}
}
public class T_command {
public static void main(String[] args) {
// 创建订单
Order order1 = new Order();
order1.setTableNum(10);
order1.getFoodMenu().put("鲍鱼炒饭", 1);
order1.getFoodMenu().put("海参炒面", 1);
Order order2 = new Order();
order2.setTableNum(15);
order2.getFoodMenu().put("回锅肉盖饭", 1);
order2.getFoodMenu().put("木须肉盖饭", 1);
// 创建接收者
Chef chef = new Chef();
// 将订单和接收者封装成命令对象
ICommand cmd1 = new CommandOrder(chef, order1);
ICommand cmd2 = new CommandOrder(chef, order2);
// 创建调用者
Waiter waiter = new Waiter();
waiter.setCommand(cmd1);
waiter.setCommand(cmd2);
// 执行命令
waiter.orderUp();
}
}
4. 应用实例
public abstract class AbsCommand {
public abstract void doIt();
public abstract void undo();
}
class CopyCommand extends AbsCommand {
Content c;
public CopyCommand(Content c) {
this.c = c;
}
@Override
public void doIt() {
c.msg = c.msg + c.msg;
}
@Override
public void undo() {
c.msg = c.msg.substring(0, c.msg.length() / 2);
}
}
class DeleteCommand extends AbsCommand {
Content c;
String deleted;
public DeleteCommand(Content c) {
this.c = c;
}
@Override
public void doIt() {
deleted = c.msg.substring(0, 5);
c.msg = c.msg.substring(5);
}
@Override
public void undo() {
c.msg = deleted + c.msg;
}
}
class InsertCommand extends AbsCommand {
Content c;
String strToInsert = "http://www.mashibing.com";
public InsertCommand(Content c) {
this.c = c;
}
@Override
public void doIt() {
c.msg = c.msg + strToInsert;
}
@Override
public void undo() {
c.msg = c.msg.substring(0, c.msg.length() - strToInsert.length());
}
}
class Content {
String msg = "hello everybody ";
public static void main(String[] args) {
Content c = new Content();
AbsCommand insertCommand = new InsertCommand(c);
insertCommand.doIt();
insertCommand.undo();
AbsCommand copyCommand = new CopyCommand(c);
copyCommand.doIt();
copyCommand.undo();
AbsCommand deleteCommand = new DeleteCommand(c);
deleteCommand.doIt();
deleteCommand.undo();
// 责任链
List<AbsCommand> commands = new ArrayList<>();
commands.add(new InsertCommand(c));
commands.add(new CopyCommand(c));
commands.add(new DeleteCommand(c));
for (AbsCommand comm : commands) {
comm.doIt();
}
System.out.println(c.msg);
for (int i = commands.size() - 1; i >= 0; i--) {
commands.get(i).undo();
}
System.out.println(c.msg);
}
}
5. 总结
1. 优点
- 降低系统耦合度。命令模式能将调用对象、执行对象解耦
- 增加、删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令
2. 缺点
- 使用命令模式可能会导致某些系统有过多的具体命令类
- 系统结构更加复杂
3. 使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互
- 系统需要在不同的时间指定请求、将请求排队和执行请求
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作
8. 中介者模式
- 中介模式(Mediator Pattern)定义:定义一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互
- 处理对象与对象之间的直接交互,封装了多个对象之间的交互细节
- 设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系转换为一对多的星状关系
- 原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码的复杂度,提高代码的可读性、可维护性
1. 介绍
- 提到中介模式,有一个比较经典的例子就是航空管制。为了让飞机在飞行时互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通信。飞机通信形成的通信网络就会无比复杂
- 这时,通过引入“塔台”这样一个中介,让每架飞机只跟塔台来通信,发送自己的位置给塔台,由塔台来负责每架飞机的航线调度。大大简化通信网络
2. 原理
- 抽象中介者角色(Mediator):中介者接口。提供了同事对象注册与转发同事对象信息的抽象方法
- 具体中介者角色(ConcreteMediator):中介者接口实现类。定义一个
List
来管理同事对象,协调各个同事角色之间的交互关系,依赖于同事角色 - 抽象同事类角色(Colleague):同事类接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能
- 具体同事类角色(Concrete Colleague):抽象同事类实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互
3. 实现
1. Mediator
/**
* 抽象中介者
*/
public interface IMediator {
// 处理同事对象注册与转发同事对象信息的方法
void apply(String key);
}
/**
* 具体中介者
*/
public class MediatorImpl implements IMediator {
@Override
public void apply(String key) {
System.out.println("最终中介者执行的操作, key为" + key);
}
}
2. Colleague
/**
* 抽象同事类
*/
@Getter
public abstract class AbsColleague {
private final IMediator mediator;
public AbsColleague(IMediator mediator) {
this.mediator = mediator;
}
// 同事间进行交互的抽象方法
public abstract void exec(String key);
}
/**
* 具体同事类
*/
public class ColleagueA extends AbsColleague {
public ColleagueA(IMediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("=> 在A同事中, 通过中介者执行!");
getMediator().apply(key);
}
}
/**
* 具体同事类
*/
public class ColleagueB extends AbsColleague {
public ColleagueB(IMediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("=> 在B同事中, 通过中介者执行!");
getMediator().apply(key);
}
}
@Test
public void test1() {
// 创建中介者
IMediator mediator = new MediatorImpl();
// 创建同事对象
AbsColleague c1 = new ColleagueA(mediator);
c1.exec("key-A");
AbsColleague c2 = new ColleagueB(mediator);
c2.exec("key-B");
}
4. 应用实例
- 租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者
1. 中介
/**
* 抽象中介者
*/
public abstract class AbsMediator {
// 创建联络方法
public abstract void contact(String message, AbsPerson person);
}
/**
* 具体的中介者 - 中介机构
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class MediatorStructure extends AbsMediator {
// 房主
private List<HouseOwner> houseOwners;
// 租客
private List<Tenant> tenants;
@Override
public void contact(String msg, AbsPerson person) {
// 如果是房主, 则租客获得信息
if (person instanceof HouseOwner) {
tenants.forEach(tenant -> tenant.receiveMsg(msg));
}
// 如果是租客, 则房东获得信息
else {
houseOwners.forEach(houseOwner -> houseOwner.receiveMsg(msg));
}
}
}
2. 房东、租客
@AllArgsConstructor
public abstract class AbsPerson {
protected String name;
// 持有中介者的引用
protected AbsMediator mediator;
public abstract void receiveMsg(String msg);
}
/**
* 具体同事类 - 房东
*/
public class HouseOwner extends AbsPerson {
public HouseOwner(String name, AbsMediator mediator) {
super(name, mediator);
}
// 与中介联系
public void contact(String message) {
mediator.contact(message, this);
}
@Override
public void receiveMsg(String message) {
System.out.println("房主类: " + name + ", 收到信息: " + message);
}
}
/**
* 具体同事类 - 租客
*/
public class Tenant extends AbsPerson {
public Tenant(String name, AbsMediator mediator) {
super(name, mediator);
}
public void contact(String message) {
mediator.contact(message, this);
}
@Override
public void receiveMsg(String message) {
System.out.println("租客类: " + name + ", 收到信息: " + message);
}
}
@Test
public void test2() {
// 中介机构
MediatorStructure mediator = new MediatorStructure();
// 房主
HouseOwner houseOwner = new HouseOwner("张三", mediator);
List<HouseOwner> houseOwners = Arrays.asList(houseOwner);
// 租客
Tenant tenant = new Tenant("李四", mediator);
List<Tenant> tenants = Arrays.asList(tenant);
// 中介收集房主及租客信息
mediator.setHouseOwners(houseOwners);
mediator.setTenants(tenants);
// 租客需求
tenant.contact("需要在天通苑附近找一个, 两室一厅的房子一家人住, 房租在5000~6000之间");
// 房主需求
houseOwner.contact("出租一套天通苑地跌站附近的两室一厅, 房租6000, 可谈");
}
5. 总结
1. 优点
- 简化了对象之间的交互,中介者、同事的一对多代替了同事间的多对多交互,一对多关系更好理解易于维护、扩展,将原本难以理解的网状结构转换成相对简单的星型结构
- 将各同事对象进行解耦。有利于各个同事间的松耦合,可以独立改变、复用每一个同事或中介者,增加新的中介者类、同事类都比较方便,符合开闭原则
- 可以减少子类生成,中介者将原本分布于多个对象的行为集中在一起,改变这些行为只需要生成新的中介者子类即可,使得同事类可以被重用,无需直接对同事类进行扩展
2. 缺点
- 具体中介者类中包含了大量同事之间的交互细节,可能会导致中介者类变得非常的复杂,使系统不好维护
3. 使用场景
- 系统中,对象间存在复杂引用关系,系统结构混乱且难以理解
- 一个对象由于引用了其他的很多对象并且直接和这些对象进行通信,导致难以复用该对象
- 通过一个中间类来封装多个类的行为,而又不想生成太多的子类,此时可以通过引入中介者类来实现,在中介者类中定义对象交互的公共行为,如果需要改变行为则可以再增加新的中介类
9. 备忘录模式
- 备忘录模式(Memento Pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,可以以后将对象恢复到原先保存的状态
1. 介绍
- 备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计
- 很多软件都提供了撤销(Undo)操作。eg:Word、记事本、Photoshop、IDEA等软件在编辑时按
Ctrl+Z
组合键时能撤销当前操作,使文档恢复到之前的状态;浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类
2. 原理
- 发起人角色(Originator):状态需要被记录的元对象类,记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息
- 备忘录角色(Memento):存储发起人的内部状态,需要时提供这些内部状态给发起人
- 看护人角色(Caretaker):对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改
3. 实现
1. Originator
/**
* 发起人角色
*/
@Data
@NoArgsConstructor
public class Originator {
private String state = "原始对象";
private String id;
private String name;
private String phone;
// 创建备忘录对象
public Memento createMemento() {
return new Memento(id, name, phone);
}
// 恢复对象
public void restoreMemento(Memento m) {
this.state = m.getState();
this.id = m.getId();
this.name = m.getName();
this.phone = m.getPhone();
}
}
2. Memento
/*
* 备忘录角色
* 访问权限为: 默认, 在同包下可见(尽量保证只有发起者类可以访问备忘录类)
*/
@Data
@NoArgsConstructor
class Memento {
private String state = "从备份对象恢复原始对象";
private String id;
private String name;
private String phone;
public Memento(String id, String name, String phone) {
this.id = id;
this.name = name;
this.phone = phone;
}
}
3. Caretaker
/**
* 负责人类 - 获取和保存备忘录对象
*/
@Data
public class Caretaker {
private Memento memento;
}
@Test
public void test1() {
// 创建发起人对象
Originator originator = new Originator();
originator.setId("1");
originator.setName("spike");
originator.setPhone("13522777722");
System.out.println("========" + originator);
// 创建负责人对象
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.createMemento());
// 修改
originator.setName("update");
System.out.println("========" + originator);
// 从负责人对象中获取备忘录对象, 实现恢复操作
originator.restoreMemento(caretaker.getMemento());
System.out.println("========" + originator);
}
4. 应用实例
1. Game
设计一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下
- 游戏玩家通过扔骰子来决定下一个状态
- 当点数为1,玩家金钱增加
- 当点数为2,玩家金钱减少
- 当点数为6,玩家会得到水果
- 当钱消耗到一定程度,就恢复到初始状态
/**
* 备份玩家的状态
*/
public class Memento {
// 获取当前玩家的金币
@Getter
private final int money;
// 玩家获取的水果
ArrayList<String> fruits;
public Memento(int money) {
this.money = money;
this.fruits = new ArrayList<>();
}
// 获取当前玩家的水果
List<String> getFruits() {
return (List<String>) fruits.clone();
}
// 添加水果
void addFruit(String fruit) {
fruits.add(fruit);
}
}
/**
* 玩家类
*/
@ToString
public class Player {
// 获取当前所有金币
@Getter
private int money;
// 玩家获得的水果
private List<String> fruits = new ArrayList<>();
// 表示水果种类的数组
private static final String[] fruitsName = {"苹果", "葡萄", "香蕉", "橘子"};
Random random = new Random();
public Player(int money) {
this.money = money;
}
// 获取一个水果
public String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
// 从数组中拿一个水果
String f = fruitsName[random.nextInt(fruitsName.length)];
return prefix + f;
}
// 掷骰子方法
public void yacht() {
int dice = random.nextInt(6) + 1; // 掷骰子
if (dice == 1) {
money += 100;
System.out.println("所持有的金币增加了...");
} else if (dice == 2) {
money /= 2;
System.out.println("所持有的金币减少一半");
}
// 获取水果
else if (dice == 6) {
String fruit = getFruit();
System.out.println("获取了水果: " + fruit);
fruits.add(fruit);
}
// 其他结果
else {
System.out.println("无效数字, 继续投掷!");
}
}
// 拍摄快照
public Memento createMemento() {
Memento memento = new Memento(money);
for (String fruit : fruits) {
// 判断: 只保存 '好吃的' 水果
if (fruit.startsWith("好吃的")) {
memento.addFruit(fruit);
}
}
return memento;
}
// 撤销方法
public void restoreMemento(Memento memento) {
this.money = memento.getMoney();
this.fruits = memento.getFruits();
}
}
@Test
public void test2() throws InterruptedException {
// 创建玩家类, 设置初始金币
Player player = new Player(100);
// 创建备忘录对象
Memento memento = player.createMemento();
for (int i = 0; i < 100; i++) {
// 显示扔骰子的次数
System.out.println("第" + i + "次投掷!");
// 显示当前玩家状态
System.out.println("当前状态: " + player);
// 开启游戏
player.yacht();
System.out.println("玩家所持有的金币: " + player.getMoney() + " 元");
// 复活操作
if (player.getMoney() > memento.getMoney()) {
System.out.println("赚到金币, 保存当前状态, 继续游戏!");
memento = player.createMemento(); // 更新快照
} else if (player.getMoney() < memento.getMoney() / 2) {
System.out.println("所持金币不多, 将游戏恢复到初始状态!");
player.restoreMemento(memento);
}
Thread.sleep(1_000);
}
}
2. Person
@ToString
@Builder
public class Person implements Serializable {
private final int id;
private final String name;
private final Location loc;
}
/**
* Serializable
* Serializable: 持久化, 磁盘IO, redis IO
* protoBuffer: 用于网络IO
* transient: 修饰不序列化属性
*/
@Builder
@ToString
class Location implements Serializable {
private final String street;
private final String roomNo;
}
class Z_main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
Location loc = Location.builder()
.street("北京,昌平")
.roomNo("101")
.build();
Person person = Person.builder()
.id(131409040)
.name("lisongtao")
.loc(loc).build();
File file = new File("/Users/listao/Documents/memento.data");
// output: 记录对象的某个瞬间
ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(file));
output.writeObject(person);
output.writeObject(loc);
// input: 顺序写入, 顺序读取
ObjectInputStream input = new ObjectInputStream(new FileInputStream(file));
Object o = input.readObject();
System.out.println(o);
Object o1 = input.readObject();
System.out.println(o1);
}
}
5. 总结
1. 优点
- 提供了一种状态恢复的实现机制,使用户方便的回到一个特定的历史步骤。当新的状态无效或存在问题时,可以使用暂存起来的备忘录将状态复原
- 一个备忘录对象是一种发起者对象状态的表示,不会被其他代码所改动。采用集合来存储备忘录可以实现多次撤销
2. 缺点
- 资源消耗过大,如果成员变量比较多,占用大量的存储空间
3. 使用场景
- 保存一个对象在某一时刻的状态时,可以使用备忘录模式
- 不希望外界直接访问对象内部状态时
10. 迭代器模式
- 迭代器模式(Iterator Pattern)又叫游标(Cursor)模式,原始定义:迭代器提供一种对容器对象中的各个元素进行访问的方法,又不需要暴露该对象内部细节
1. 介绍
- 设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。绝大多数编程语言中,迭代器已经成为一个基础的类库,直接用来遍历集合对象。平时开发,更多的是直接使用它,很少会从零去实现一个迭代器
- 在软件系统中,容器对象拥有两个职责:1. 存储数据;2. 遍历数据。从依赖性上看,前者是聚合对象的基本职责。而后者是可变化的,又是可分离的
- 可以将遍历数据的行为从容器中抽取出来,封装到迭代器对象中,由迭代器来提供遍历数据的行为,简化聚合对象的设计,符合单一职责原则
2. 原理
- 抽象集合角色(Aggregate):用于存储和管理元素对象,定义存储、添加、删除集合元素,并且声明
createIterator()
用于创建迭代器对象 - 具体集合角色(ConcreteAggregate):实现抽象集合类,返回一个具体迭代器实例
- 抽象迭代器角色(Iterator):定义访问和遍历聚合元素的接口,通常包含
hasNext()
、next()
等方法hasNext()
:判断集合中是否还有下一个元素next()
:将游标后移一位元素currentItem()
:返回当前游标指向的元素
- 具体迭代器角色(ConcreteIterator):实现抽象迭代器接口中所定义的方法,完成对集合对象的遍历,同时记录遍历的当前位置
3. 实现
/**
* 迭代器接口
*/
public interface Iterator<E> {
// 判断集合中是否有下一个元素
boolean hasNext();
// 将有游标后移一位
void next();
// 返回当前游标指定的元素
E currentItem();
}
/**
* 具体的迭代器
*/
public class ConcreteIterator<E> implements Iterator<E> {
// 游标
private int cursor;
// 容器
private final List<E> list;
public ConcreteIterator(List<E> list) {
this.cursor = 0;
this.list = list;
}
@Override
public boolean hasNext() {
return cursor != list.size();
}
@Override
public void next() {
cursor++;
}
@Override
public E currentItem() {
if (cursor >= list.size()) {
throw new NoSuchElementException();
}
return list.get(cursor);
}
}
@Test
public void test1() {
List<String> names = Arrays.asList("lisi", "zhangsan", "wangwu");
Iterator<String> iterator = new ConcreteIterator<>(names);
while (iterator.hasNext()) {
System.out.println(iterator.currentItem());
iterator.next();
}
}
4. 应用实例
1. OoxxIterator
1. Iterator
/**
* 抽象迭代器
*/
public interface IIterator<E> {
// 检索当前元素
E currentItem();
// 获取下一个元素
E next();
// 判断集合中是否还有下一个元素
boolean hasNext();
// 重置为第一个元素
void reset();
}
/**
* 具体迭代器
*/
public class OoxxIterator implements IIterator<Ooxx> {
// Topic数组
private final Ooxx[] ooxxs;
// 记录存储位置的有游标
private int position;
public OoxxIterator(Ooxx[] ooxxs) {
position = 0;
this.ooxxs = ooxxs;
}
@Override
public Ooxx currentItem() {
return ooxxs[position];
}
@Override
public boolean hasNext() {
if (position >= ooxxs.length) {
return false;
}
return true;
}
@Override
public Ooxx next() {
return ooxxs[position++];
}
@Override
public void reset() {
position = 0;
}
}
2. List
/**
* 抽象集合
*/
public interface IList<E> {
// 获取迭代器对象的抽象方法
IIterator<E> iterator();
}
/**
* 具体集合类
*/
public class OoxxList implements IList<Ooxx> {
private final Ooxx[] ooxxs;
public OoxxList(Ooxx[] ooxxs) {
this.ooxxs = ooxxs;
}
@Override
public IIterator<Ooxx> iterator() {
return new OoxxIterator(ooxxs);
}
}
@Test
public void test2() {
Ooxx[] topics = new Ooxx[4];
topics[0] = new Ooxx("t1");
topics[1] = new Ooxx("t2");
topics[2] = new Ooxx("t3");
topics[3] = new Ooxx("t4");
OoxxList topicList = new OoxxList(topics);
IIterator<Ooxx> iterator = topicList.iterator();
while (iterator.hasNext()) {
Ooxx topic = iterator.next();
System.out.println(topic.getName());
}
}
2. Iterator_
1. Collection接口
interface Collection_ {
void add(Object o);
int size();
}
// -------------------------------------------------------
class ArrayList_ implements Collection_ {
Object[] objects = new Object[10];
private int index = 0;
@Override
public void add(Object o) {
if (index == objects.length) {
Object[] newObjects = new Object[objects.length * 2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index++;
}
@Override
public int size() {
return index;
}
}
// -------------------------------------------------------
class LinkedList_ implements Collection_ {
Node head = null;
Node tail = null;
private int size = 0;
@Override
public void add(Object o) {
Node n = new Node(o);
n.next = null;
if (head == null) {
head = n;
tail = n;
}
tail.next = n;
tail = n;
size++;
}
@Override
public int size() {
return size;
}
private static class Node {
Object o;
Node next;
public Node(Object o) {
this.o = o;
}
}
}
// -------------------------------------------------------
/**
* - 添加容器的共同接口,实现容器的替换
* - 如何对容器遍历呢?
*/
public class Main {
public static void main(String[] args) {
// Collection_ list = new ArrayList_();
Collection_ list = new LinkedList_();
for (int i = 0; i < 15; i++) {
list.add("s" + i);
}
System.out.println(list.size());
ArrayList_ al = (ArrayList_) list;
for (int i = 0; i < al.size(); i++) {
// 如果用这种遍历方式,就不能实现通用了
}
}
}
2. Iterator接口
/**
* 统一的遍历方式,要求每一个容器都要提供Iterator的实现类
* 作业:实现LinkedList的Iterator
*/
interface Collection_ {
void add(Object o);
int size();
Iterator_ iterator();
}
// -------------------------------------------------------
interface Iterator_ {
boolean hasNext();
Object next();
}
// -------------------------------------------------------
class ArrayList_ implements Collection_ {
Object[] objects = new Object[10];
private int index = 0;
public void add(Object o) {
if (index == objects.length) {
Object[] newObjects = new Object[objects.length * 2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index++;
}
public int size() {
return index;
}
@Override
public Iterator_ iterator() {
return new ArrayListIterator();
}
/**
* iterator内部类
*/
private class ArrayListIterator implements Iterator_ {
private int currentIndex = 0;
@Override
public boolean hasNext() {
return currentIndex < index;
}
@Override
public Object next() {
Object o = objects[currentIndex];
currentIndex++;
return o;
}
}
}
// -------------------------------------------------------
class LinkedList_ implements Collection_ {
Node head = null;
Node tail = null;
private int size = 0;
public void add(Object o) {
Node n = new Node(o);
n.next = null;
if (head == null) {
head = n;
tail = n;
}
tail.next = n;
tail = n;
size++;
}
private static class Node {
Object o;
Node next;
public Node(Object o) {
this.o = o;
}
}
public int size() {
return size;
}
@Override
public Iterator_ iterator() {
return new LinkedListIterator();
}
/**
* iterator内部类
*/
private class LinkedListIterator implements Iterator_ {
private int currentIndex = 0;
private Node currNode = head;
@Override
public boolean hasNext() {
return currentIndex < size;
}
@Override
public Object next() {
Object o = currNode.o;
currNode = currNode.next;
currentIndex++;
return o;
}
}
}
// -------------------------------------------------------
public class Main {
public static void main(String[] args) {
Collection_ list = new LinkedList_();
for (int i = 0; i < 15; i++) {
list.add("s" + i);
}
System.out.println(list.size());
// 这个接口的调用方式:
Iterator_ it = list.iterator();
while (it.hasNext()) {
Object o = it.next();
System.out.println(o);
}
}
}
3. 泛型
/**
* 实现泛型
*/
interface Collection_<E> {
void add(E o);
int size();
Iterator_<E> iterator();
}
interface Iterator_<E> {
boolean hasNext();
E next();
}
@SuppressWarnings("unchecked")
class ArrayList_<E> implements Collection_<E> {
E[] objects = (E[]) new Object[10];
private int index = 0;
public void add(E o) {
if (index == objects.length) {
E[] newObjects = (E[]) new Object[objects.length * 2];
System.arraycopy(objects, 0, newObjects, 0, objects.length);
objects = newObjects;
}
objects[index] = o;
index++;
}
public int size() {
return index;
}
@Override
public Iterator_<E> iterator() {
return new ArrayListIterator();
}
private class ArrayListIterator implements Iterator_<E> {
private int currentIndex = 0;
@Override
public boolean hasNext() {
return currentIndex < index;
}
@Override
public E next() {
E o = objects[currentIndex];
currentIndex++;
return o;
}
}
}
class LinkedList_<E> implements Collection_<E> {
Node head = null;
Node tail = null;
private int size = 0;
public void add(E o) {
Node n = new Node(o);
n.next = null;
if (head == null) {
head = n;
tail = n;
}
tail.next = n;
tail = n;
size++;
}
class Node {
E o;
Node next;
public Node(E o) {
this.o = o;
}
}
public int size() {
return size;
}
@Override
public Iterator_<E> iterator() {
return new LinkedListIterator();
}
/**
* iterator内部类
*/
private class LinkedListIterator implements Iterator_<E> {
private int currentIndex = 0;
private Node currNode = head;
@Override
public boolean hasNext() {
return currentIndex < size;
}
@Override
public E next() {
E o = currNode.o;
currNode = currNode.next;
currentIndex++;
return o;
}
}
}
public class Main {
public static void main(String[] args) {
Collection_<String> list = new ArrayList_<>();
for (int i = 0; i < 15; i++) {
list.add("s" + i);
}
System.out.println(list.size());
Iterator_<String> it = list.iterator();
while (it.hasNext()) {
String o = it.next();
System.out.println(o);
}
}
}
5. 总结
1. 优点
- 同一个集合对象,支持以不同方式遍历。只需要用一个不同的迭代器来替换原有迭代器,即可改变遍历算法,也可以自定义迭代器的子类
- 简化了集合类设计。由于引入了迭代器,在原有的集合对象中不需要再自行提供数据遍历等方法
- 符合《开闭原则》。引入了抽象层,增加新的集合类和迭代器类都很方便,无须修改原有代码
2. 缺点
- 将存储数据和遍历数据进得职责分离,增加了类的个数,一定程度上增加了系统的复杂性
- 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展
3. 使用场景
减少程序中重复的遍历代码
- 如果不将遍历算法封装到容器里(eg:List、Set、Map等),就需要使用容器的人自行去实现遍历算法,容易造成很多重复的循环和条件判断语句出现,不利于代码的复用和扩展,同时还会暴露不同容器的内部结构
- 迭代器模式是将遍历算法作为容器对象自身的一种“属性方法”使用,能够有效地避免写很多重复的代码,同时又不会暴露内部结构
为遍历不同的集合结构提供一个统一的接口、当访问一个集合对象的内容而无须暴露其内部细节
迭代器模式把对不同集合类的访问逻辑抽象出来,这样在不用暴露集合内部结构的情况下,可以隐藏不同集合遍历需要使用的算法,同时还能够对外提供更为简便的访问算法接口
11. 解释器模式
- 解释器模式(Interpreter Pattern)原始定义:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法
1. 介绍
- 解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。只在一些特定的领域被用到。eg:编译器、规则引擎、正则表达式、SQL解析等
- 不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑
假设设计一个软件用来进行加减计算。第一想法就是使用工具类,提供对应的加法和减法的工具方法。
// 两个整数相加的方法
public static int add(int a ,int b){
return a + b;
}
// 三个整数相加的方法
public static int add(int a, int b, int c){
return a + b + c;
}
public static int add(Integer ... arr){
int sum = 0;
for(Integer num : arr){
sum += num;
}
return sum;
}
- 上面形式比较单一、有限,如果形式变化非常多,就不符合要求,因为加法、减法,两个运算符与数值可以有无限种组合方式。eg:
5-3+2-1
,10-5+20
...
1. 文法规则
- 解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子
- 文法:用于描述语言的语法结构的形式规则
在上面提到的加法 / 减法解释器中,每一个输入表达式(eg:2+3+4-5
)都包含了3个语言单位,以下文法规则定义
expression ::= value | plus | minus
plus ::= expression ‘+’ expression
minus ::= expression ‘-’ expression
value ::= integer
- “::=” 表示 “定义为”
- 竖线 | 表示或,左右的其中一个
- 引号内为字符本身
- 引号外为语法
规则描述为:
- 表达式可以是一个值,也可以是plus、minus运算
- plus、minus又是由表达式结合运算符构成
- 值的类型为整型数。
2. 抽象语法树
- 解释器模式中,通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例
- eg:加法 / 减法表达式的语句
1 + 2 + 3 - 4 + 1
通过抽象语法树表示
- eg:加法 / 减法表达式的语句
2. 原理
- 抽象表达式角色(Abstract Expression):解释器接口,约定解释器的解释操作,主要包含解释方法
interpret()
- 终结符表达式角色(Terminal Expression):抽象表达式的子类,实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的
value
是终结符表达式 - 非终结符表达式角色(Nonterminal Expression):抽象表达式的子类,实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。上例中的
plus, minus
都是非终结符表达式 - 环境角色(Context):包含各个解释器需要的数据或公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值
- 客户端(Client):主要任务是将需要分析的句子、表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,也可以通过环境角色间接访问解释器的解释方法
3. 实现
定义了一个进行加减乘除计算的“语言”,语法规则如下:
- 运算符只包含加、减、乘、除,并且没有优先级的概念
- 表达式中,先书写数字,后书写运算符,空格隔开
1. 常规写法
eg:9 5 7 3 - + *
:
- 数字
9, 5
和-
运算符,计算得到4 -->4 7 3 + *
- 取出
4, 7
和+
运算符,计算得到11 -->11 3 *
- 取出
11, 3
和*
运算符,结果 33
/**
* 表达式解释器
*/
public class ExpressionInterpreter {
// Deque双向队列, 可以从队列两端增加或删除元素
private final Deque<Long> numbers = new LinkedList<>();
// 接收表达式进行解析
public long interpret(String expression) {
// 9 5 7 3 - + *
String[] elements = expression.split(" ");
int length = elements.length;
// 获取表达式中的数字
for (int i = 0; i < (length + 1) / 2; ++i) {
// 向集合的尾部添加元素
numbers.addLast(Long.parseLong(elements[i]));
}
// 获取表达式中的符号, 进行计算
for (int i = (length + 1) / 2; i < length; ++i) {
String operator = elements[i];
// 符号必须是 + - * /, 否则抛出异常
boolean isValid = "+".equals(operator) || "-".equals(operator) ||
"*".equals(operator) || "/".equals(operator);
if (!isValid) {
throw new RuntimeException("无效表达式!" + expression);
}
// 获取集合中的数字, 移除集合中的第一个元素, 并且返回被移除的值
long number1 = numbers.pollFirst(); // 数字
long number2 = numbers.pollFirst(); // 数字
long result = 0;
if ("+".equals(operator)) {
result = number1 + number2;
} else if ("-".equals(operator)) {
result = number1 - number2;
} else if ("*".equals(operator)) {
result = number1 * number2;
} else if ("/".equals(operator)) {
result = number1 / number2;
}
// 将运算结果添加到集合头部
numbers.addFirst(result);
}
// 运算的最终结果是被保存在集合中的
if (numbers.size() != 1) {
throw new RuntimeException("无效表达式!" + expression);
}
// 移除集合中的唯一的结果, 并返回
return numbers.pop();
}
}
2. 解释器代码重构
- 所有的解析逻辑都耦合在一个函数中,显然是不合适的。考虑拆分代码,将解析逻辑拆分到独立的小类中,前面定义的语法规则有两类表达式
- 数字
- 运算符(加、减、乘、除)
- 利用解释器模式,把解析工作拆分到以下五个类
/**
* 表达式接口
*/
public interface IExpression {
long interpret();
}
/**
* 数字表达式
*/
public class ExpNum implements IExpression {
private final long number;
public ExpNum(long number) {
this.number = number;
}
public ExpNum(String number) {
this.number = Long.parseLong(number);
}
@Override
public long interpret() {
return this.number;
}
}
/**
* 加法运算
*/
@AllArgsConstructor
public class ExpPlus implements IExpression {
private IExpression exp1;
private IExpression exp2;
@Override
public long interpret() {
return exp1.interpret() + exp2.interpret();
}
}
/**
* 减法运算
*/
@AllArgsConstructor
public class ExpSub implements IExpression {
private IExpression exp1;
private IExpression exp2;
@Override
public long interpret() {
return exp1.interpret() - exp2.interpret();
}
}
/**
* 乘法法运算
*/
@AllArgsConstructor
public class ExpMul implements IExpression {
private IExpression exp1;
private IExpression exp2;
@Override
public long interpret() {
return exp1.interpret() * exp2.interpret();
}
}
/**
* 除法法运算
*/
@AllArgsConstructor
public class ExpDiv implements IExpression {
private IExpression exp1;
private IExpression exp2;
@Override
public long interpret() {
return exp1.interpret() / exp2.interpret();
}
}
/**
* 表达式解释器类
*/
public class ExpInterpreter {
private final Deque<IExpression> numbers = new LinkedList<>();
public long interpret(String expression) {
String[] elements = expression.split(" ");
int length = elements.length;
for (int i = 0; i < (length + 1) / 2; ++i) {
numbers.addLast(new ExpNum(elements[i]));
}
for (int i = (length + 1) / 2; i < length; ++i) {
String operator = elements[i];
// 符号必须是 + - * / ,否则抛出异常
boolean isValid = "+".equals(operator) || "-".equals(operator) || "*".equals(operator) || "/".equals(operator);
if (!isValid) {
throw new RuntimeException("无效表达式!" + expression);
}
IExpression exp1 = numbers.pollFirst();
IExpression exp2 = numbers.pollFirst();
IExpression result = null;
if ("+".equals(operator)) {
result = new ExpPlus(exp1, exp2);
} else if ("-".equals(operator)) {
result = new ExpSub(exp1, exp2);
} else if ("*".equals(operator)) {
result = new ExpMul(exp1, exp2);
} else if ("/".equals(operator)) {
result = new ExpDiv(exp1, exp2);
}
long num = result.interpret();
numbers.addFirst(new ExpNum(num));
}
if (numbers.size() != 1) {
throw new RuntimeException("无效的表达式!" + expression);
}
return numbers.pop().interpret();
}
}
@Test
public void test2() {
ExpInterpreter e = new ExpInterpreter();
long result = e.interpret("10 2 5 5 5 / - + *");
System.out.println("result = " + result);
}
4. 总结
1. 优点
- 易于改变、扩展文法
- 解释器模式中,使用类来表示语言的文法规则,因此可以通过继承等机制改变、扩展文法
- 实现文法比较容易
- 抽象语法树中,每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂
- 增加新的解释表达式比较方便
- 如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以。原有的表达式类不需要修改,符合开闭原则
2. 缺点
- 对于复杂文法难以维护
- 解释器中,一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,系统的维护难以管理
- 执行效率低
- 解释器模式中,大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程非常的繁琐
3. 使用场景
- 语言的文法比较简单,并且执行效率不是关键问题
- 问题重复出现,且可以用一种简单的语言来进行表达
- 一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树时