一、观察者模式
1、定义
定义对象之间的一对多的依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。
描述复杂的流程控制 | 描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务 它涉及算法与对象间职责的分配; |
类行为模式 | 采用继承机制在类之间分配行为 |
对象行为模式 | 采用组合在对象间分配行为 |
优势 | 由于组合关系或聚合关系比继承关系耦合度低,满足”合成复用原则“; 所以对象行为模式更灵活; |
2、结构
3、优缺点
优点 | 降低了观察者和目标类的耦合关系; 两者之间是抽象耦合关系; |
目标类发送通知,所有观察者都能收到消息; 可以实现广播机制; | |
缺点 | 如果观察者非常多,那么所有的观察者收到消息会耗时; |
如果观察者循环依赖的话,那么目标类发送通知会使观察者循环调用,导致系统崩溃; |
4、使用场景
当一个对象状态的改变需要改变其他对象时;比如:库存变化时,要通知商品页面;
一个对象只想发送通知,不用管谁能收到;比如:朋友圈,微博;
需要创建一种链式触发机制;A改变B,B改变C,...
5、示例代码
/**
* 抽象观察者
*/
public interface Observer {
// 为不同的观察者更新行为定义一个相同的接口
// 不同的观察者对该方法由不同的实现
public void update();
}
/**
* 具体观察者 1
*/
public class ConcreteObserver_1 implements Observer{
@Override
public void update() {
System.out.println("1 == 得到通知,更新状态");
}
}
/**
* 具体观察者 2
*/
public class ConcreteObserver_2 implements Observer{
@Override
public void update() {
System.out.println("2 == 得到通知,更新状态");
}
}
/**
* 抽象目标类
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers(); // 通知被观察者
}
/**
* 具体目标类
*/
public class ConcreteSubject implements Subject {
// 定义集合,存储所有的观察者对象
private final ArrayList<Observer> list = new ArrayList<>();
// 注册,向观察者集合添加一个观察者
@Override
public void attach(Observer observer) {
list.add(observer);
}
// 注销,移除一个观察者
@Override
public void detach(Observer observer) {
list.remove(observer);
}
// 通知
@Override
public void notifyObservers() {
// 调用每个观察者的响应
for (Observer observer : list) {
observer.update();
}
}
}
public class Client {
public static void main(String[] args) {
// 创建目标类
Subject subject = new ConcreteSubject();
// 注册观察者
ConcreteObserver_1 concreteObserver1 = new ConcreteObserver_1();
subject.attach(concreteObserver1);
subject.attach(new ConcreteObserver_2());
// 通知
subject.notifyObservers();
// 注销
subject.detach(concreteObserver1);
// 通知
subject.notifyObservers();
}
}
二、模板方法模式
1、定义
在操作中定义业务逻辑的模板,将一些逻辑代码放到子类中实现。
模板方法模式让子类在不改变业务逻辑结构的情况下重新定义业务的某些步骤。
举例:就诊挂号、取号、排队、就诊。
是一种基于继承的代码复用技术,是类行为模式。
结构中只存在父类与子类之间的继承关系,主要作用是提高代码的复用性和扩展性。
2、结构
3、优缺点
优点 | 在父类中形式化地定义一个算法,而由它的子类来实现细节处理; 在子类实现详细的处理代码时,不会改变父类算法中步骤的执行顺序; |
模板方法可以实现一种反向的控制结构,通过子类覆盖父类的钩子方法,来决定某一个特定的步骤是否需要执行; | |
在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现; 更换和新增子类很方便,符合单一职责原则和开闭原则; | |
缺点 | 需要对每一个不同的实现定义单独的子类,会导致类的数量增加,设计更加抽象; |
父类中的抽象方法由子类实现,子类的执行结果会影响父类的结果,导致了一种反向的控制结构,提高了代码的阅读难度; |
4、使用场景
多个类有相同的方法,并且逻辑可以共用。
将通用的算法或固定流程设计为模板,在每一个具体的子类中再继续优化算法步骤或流程时。
重构超长代码,发现某一个经常使用的公有方法。
5、示例代码
/**
* 抽象模板角色
*/
public abstract class AbstractTemplate {
void step_1(String key){
System.out.println("在模板类中 -> 执行步骤 1");
if (step_2(key)){
step_3();
}else {
step_4();
}
step_5();
}
boolean step_2(String key){
System.out.println("在模板类中 -> 执行步骤 2");
if ("x".equals(key)){
return true;
}
return false;
}
abstract void step_3();
abstract void step_4();
void step_5(){
System.out.println("在模板类中 -> 执行步骤 5");
}
void run(String key){
step_1(key);
}
}
/**
* 实现类A
*/
public class ConcreteClassA extends AbstractTemplate{
@Override
void step_3() {
System.out.println("在子类A中 -> 执行步骤 3");
}
@Override
void step_4() {
System.out.println("在子类A中 -> 执行步骤 4");
}
}
/**
* 实现类B
*/
public class ConcreteClassB extends AbstractTemplate{
@Override
void step_3() {
System.out.println("在子类B中 -> 执行步骤 3");
}
@Override
void step_4() {
System.out.println("在子类B中 -> 执行步骤 4");
}
}
public class Client {
public static void main(String[] args) {
AbstractTemplate classA = new ConcreteClassA();
classA.run("x");
System.out.println("===================");
classA.run("123");
System.out.println("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");
AbstractTemplate classB = new ConcreteClassB();
classB.run("x");
System.out.println("===================");
classB.run("123");
}
}
三、策略模式
1、定义
定义一系列算法,将每个算法封装起来,并且使它们可以相互替换。
策略模式让算法可以独立于使用它的客户端而变化。
举例:出行选择不同的交通工具。
2、结构
3、优缺点
优点 | 策略之间可以自由切换; 具体策略类都实现抽象策略类,所以它们之间可以自由切换; |
易于扩展; 增加一个新的具体策略类不需要改变原有代码,符合开闭原则; | |
避免了使用多重分支语句; | |
缺点 | 客户端必须知道所有的策略类,并且自行决定使用哪一个策略类; |
策略模式会造成有很多策略类,可以通过享元模式减少这些对象的数量; |
4、使用场景
一个系统需要动态地在几种算法中选择一种时,可以将每个算法单独封装到策略类中。
一个类定义了多种行为,并且出现了多个条件语句,可以将每个条件的逻辑封装到各自的策略类中。
想让客户端不知道算法操作的数据时,使用策略模式隐藏与算法相关的数据结构。
5、示例代码
/**
* 抽象策略类
*/
public abstract class Strategy {
abstract void algorithm();
}
/**
* 具体策略A
*/
public class ConcreteStrategyA extends Strategy{
@Override
void algorithm() {
System.out.println("具体策略A");
}
}
/**
* 具体策略B
*/
public class ConcreteStrategyB extends Strategy{
@Override
void algorithm() {
System.out.println("具体策略B");
}
}
/**
* 上下文类
* 策略模式的本质:通过上下文类作为控制单元,对不同的策略进行调度分配;
*/
public class Context {
// 维护一个抽象策略的引用
private final Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
// 调用具体策略对象的算法
public void algorithm(){
strategy.algorithm();
}
}
public class Client {
public static void main(String[] args) {
Strategy concrete = new ConcreteStrategyB();
Context context = new Context(concrete);
context.algorithm();
}
}
四、职责链模式 / 责任链模式
1、定义
模型:审批流程
原始定义:避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求,将接受请求的对象链接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
2、结构
3、优缺点
优点 | 降低了对象之间的耦合度; 降低了请求发送者和接收者的耦合度; |
增强了系统的可扩展性; 可以根据需求增加新的请求处理类,满足开闭原则; | |
增强了给对象指派职责的灵活性; 当工作流程发生变化,可以动态地改变链内部的成员或者修改它们的次序,也可以动态地新增或删除责任节点; | |
简化了对象之间的连接; 一个对象只需要维护一个向后的引用,避免了过多的if...else...; | |
责任分担; 每个类只需要处理自己的工作,不能处理的就交给下一个节点,符合单一职责原则; | |
缺点 | 不能保证每个请求一定被执行; 由于一个请求没有明确的接收者,所以不能保证它一定会被处理,可能到最后一个节点都没处理; |
对于比较长的责任链,请求的处理可能涉及多个节点,系统性能受到一定影响; | |
责任链的创建需要客户端控制,增加了客户端的复杂度; 可能出现循环调用; |
4、使用场景
责任链模式常被用于框架开发中,来实现框架的过滤器、拦截器等功能。让框架使用者在不修改源码的情况下,添加新的过滤拦截功能;
在运行时需要动态使用多个关联对象来处理同一次请求。比如:审批流程。
不想让使用者知道内部处理逻辑。比如:权限校验的登录拦截器。
需要动态地更换处理对象。比如:工单处理系统。
5、示例代码
/**
* 抽象处理者类
*/
@Data
public abstract class Handler {
// 下一步
protected Handler successor;
public abstract void handle(RequestData requestData);
}
/**
* 具体处理者类 A
*/
public class Handler_A extends Handler {
@Override
public void handle(RequestData requestData) {
System.out.println("A 执行代码逻辑");
System.out.println("处理 : " + requestData.getData());
requestData.setData(requestData.getData().replace("A", "_"));
// 判断是否继续向后执行
if (this.successor != null) {
// 向后执行
successor.handle(requestData);
} else {
System.out.println("执行终止:" + this.getClass());
}
}
}
/**
* 具体处理者类 A
*/
public class Handler_B extends Handler {
@Override
public void handle(RequestData requestData) {
System.out.println("B 执行代码逻辑");
System.out.println("处理 : " + requestData.getData());
requestData.setData(requestData.getData().replace("B", "_"));
// 判断是否继续向后执行
if (this.successor != null) {
// 向后执行
successor.handle(requestData);
} else {
System.out.println("执行终止:" + this.getClass());
}
}
}
/**
* 请求封装
*/
@Data
public class RequestData {
private String data;
public RequestData(String data) {
this.data = data;
}
}
/**
* 客户端
*/
public class Client {
public static void main(String[] args) {
Handler a = new Handler_A();
Handler b = new Handler_B();
Handler c = new Handler_C();
// 创建处理链
a.setSuccessor(b);
b.setSuccessor(c);
RequestData requestData = new RequestData("ABCDEFG");
// 调用处理链头部的方法
a.handle(requestData);
}
}
五、状态模式
1、定义
允许一个对象在其内部状态改变的时候,改变它的行为,这个对象看起来似乎是修改了它的类。
状态模式就是用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。
2、结构
3、优缺点
优点 | 将所有与某个状态有关的行为封装到了一个类中,并且可以方便地增加新的状态,只要改变对象状态即可改变对象的行为; |
允许状态转换逻辑与状态对象合成一体,避免了大的条件语句块; | |
缺点 | 会增加类的个数; |
结构和实现都比较复杂,如果使用不当会造成代码结构混乱; | |
对【开闭原则】支持不好,添加新的状态类需要修改上下文类的代码; |
4、使用场景
对象根据自身当前状态来进行不同行为的操作时。比如:订单状态、不希望使用大的分支语句。
5、示例代码
/**
* 抽象状态类
*/
public interface State {
// 申明抽象方法,不同具体状态可以有不同的实现
void handle(Context context);
}
/**
* 具体状态类 A
*/
public class ConcreteState_A implements State {
@Override
public void handle(Context context) {
System.out.println("进入状态模式A");
context.setCurrentState(this);
}
@Override
public String toString() {
return "当前状态:A";
}
}
/**
* 具体状态类 B
*/
public class ConcreteState_B implements State {
@Override
public void handle(Context context) {
System.out.println("进入状态模式B");
context.setCurrentState(this);
}
@Override
public String toString() {
return "当前状态:B";
}
}
/**
* 上下文类
*/
@Data
public class Context {
// 维护一个对状态对象的引用
private State currentState = null;
public Context() {
}
public Context(State currentState) {
this.currentState = currentState;
}
@Override
public String toString() {
return "Context{" +
"currentState=" + currentState +
'}';
}
}
public class Client {
public static void main(String[] args) {
// 创建模式A
ConcreteState_A state_a = new ConcreteState_A();
// 创建上下文
Context context = new Context();
// 给上下文设置状态
state_a.handle(context);
// 查看上下文维护的状态是谁
System.out.println(context.getCurrentState().toString());
System.out.println(context.toString());
System.out.println("=================================");
ConcreteState_B state_b = new ConcreteState_B();
state_b.handle(context);
System.out.println(context.getCurrentState().toString());
}
}
六、迭代器模式
1、定义
适用于对集合的遍历。
迭代器提供一种对容器对象中的各个元素进行访问的方法,而又不需要暴露该对象的内部细节。
容器对象将遍历的功能单独抽取出来,形成了迭代器。
2、结构
3、优缺点
优点 | 迭代器模式支持以不同方式遍历一个集合对象,在同一个集合对象上可以定义多种遍历方式; 在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器就可以改变遍历算法; 也可以自己定义迭代器的子类来支持新的遍历算法; |
迭代器简化了集合类;由于引入了迭代器,在原有及集合对象中不需要再自行维护数据遍历方式; | |
由于引入了抽象层,增加新的集合类和迭代器类都很方便; 满足:【开闭原则】【基于接口编程而非实现】 | |
缺点 | 迭代器模式将存储数据和遍历数据的职责分离,增加了类的个数,提高了代码复杂度; |
抽象迭代器设计难度高,需要充分考虑将来的扩展; |
4、使用场景
减少重复的遍历代码;
当需要为不同的集合提供统一的遍历接口;
当访问一个集合对象的内容,而不暴露其内部构造;
5、示例代码
/**
* 抽象迭代器角色
*/
public interface Iterator<E> {
boolean hasNext();
void next();
E currentItem();
}
/**
* 具体迭代器角色
*
* @param <E>
*/
public class ConcreteIterator<E> implements Iterator {
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 Object currentItem() {
if (cursor >= list.size()) {
throw new NoSuchElementException();
}
return list.get(cursor);
}
}
public class Client_01 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
Iterator<String> iterator = new ConcreteIterator<>(list);
while (iterator.hasNext()){
System.out.println(iterator.currentItem());
iterator.next();
}
}
}
七、访问者模式
1、定义
可能导致代码可读性、可维护性变差,非必要不推荐使用;
访问者模式解决的是数据与算法的耦合问题;
允许在运行时将一个或多个操作用在一组对象上,将操作和对象结构分离:
- 对一组对象做操作;
- 分离对象的操作和对象的结构;
2、结构
3、优缺点
优点 | 扩展性好; 不修改具体元素类的情况下,为具体元素添加新的功能; |
复用性好; 通过访问者定义整个对象结构的通用功能,提高代码复用性; | |
分离无关行为; 访问者将无关行为封装,每个访问者的功能都比较单一; | |
缺点 | 对象结构变化困难,违背【开闭原则】; 每增加一个具体元素类,都要在访问者类加上相应操作; |
违背【依赖倒置原则】; 访问者依赖了具体类,没依赖抽象类; |
4、使用场景
对象结构相对稳定,操作经常变化的时候;
需要将数据结构和不常用的操作进行分离的时候;
比如:文件扫描——文件不变,仅是外部做扫描;
5、示例代码
核心思想:
- 由访问者执行具体算法。
- 面对不同类型的对象时,由具体对象调用访问者的方法,以确定执行哪个重载方法。
/**
* 数据结构类
* -- 规定数据基本结构
*/
@Data
public abstract class Product {
private String name;
private LocalDate date; // 生产日期
private double price;
public Product(String name, LocalDate date, double price) {
this.name = name;
this.date = date;
this.price = price;
}
}
/**
* 抽象元素角色
* -- 接待者接口
*/
public interface Acceptable {
// 接收所有的visitor访问者的子类
void accept(Visitor visitor);
}
/**
* 具体数据元素
*/
public class Candy extends Product implements Acceptable {
public Candy(String name, LocalDate date, double price) {
super(name, date, price);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
/**
* 抽象访问者
*/
public interface Visitor {
void visit(Candy candy);
void visit(Wine wine);
void visit(Fruit fruit);
}
/**
* 具体访问角色
* -- 计价
*/
public class DiscountVisitor implements Visitor {
// 账单日期
private final LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
this.billDate = billDate;
System.out.println("结算日期:" + this.billDate);
}
@Override
public void visit(Candy candy) {
System.out.println("糖果:" + candy.getName());
// 糖果 >180天 禁止售卖,否则一律 9 折
long days = billDate.toEpochDay() - candy.getDate().toEpochDay();
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());
// 水果 > 7天 禁止售卖
long days = billDate.toEpochDay() - fruit.getDate().toEpochDay();
double rate = 0;
if (days > 7) {
System.out.println("超过7天的水果请勿食用");
} else if (days > 3) {
rate = 0.5;
} else {
rate = 1.0;
}
double realPrice = fruit.getPrice() * rate * fruit.getWeight();
System.out.println("水果价格:" + NumberFormat.getCurrencyInstance().format(realPrice));
}
}
public class Client_02 {
public static void main(String[] args) {
Candy candy = new Candy("德芙巧克力", LocalDate.of(2022,5,1),10.0);
Wine wine = new Wine("茅台", LocalDate.of(2022, 5, 1), 1000.0);
Fruit fruit = new Fruit("草莓", LocalDate.of(2022, 5, 1), 50.0,1);
List<Product> list = Arrays.asList(candy,wine,fruit);
DiscountVisitor visitor = new DiscountVisitor(LocalDate.of(2023, 5, 1));
// for (Product product : list) {
// visitor.visit(product); // 报错了
// }
List<Acceptable> products = Arrays.asList(candy, wine, fruit);
for (Acceptable product : products) {
product.accept(visitor);
}
}
}
八、备忘录模式
1、定义
为了实现【撤销 / 回滚】的操作;
在不破坏封装的前提下,捕获一个对象的内部状态,并且在这个对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态
2、结构
3、优缺点
优点 | 提供了一种状态恢复的实现机制,可以恢复到历史步骤; 当新的状态无效或出错的时候,可以使用暂存起来的备忘录对象恢复; |
实现了对信息的封装; 一个备忘录只会对一种发起者做存储,不会被其他代码修改; 采用集合来存储备忘录可以实现多次的撤销操作; | |
缺点 | 资源消耗过大; 每次保存新的快照,都需要消耗存储空间; |
4、使用场景
需要保存一个对象在某一时刻的状态。
不希望外界直接访问发起者对象内部状态时。
5、示例代码
/**
* 发起人角色
*/
@Data
public class Originator {
private String state = "1";
private String id;
private String name;
private String phone;
public Originator() {
}
// 创建备忘录对象
public Memento createMemento(){
return new Memento(id,name,phone);
}
// 恢复对象状态
public void restoreMemento(Memento memento){
this.state = memento.getState();
this.id = memento.getId();
this.name = memento.getName();
this.phone = memento.getPhone();
}
}
/**
* 备忘录角色
* -- 访问权限:默认,在同包下可见;
* 尽量保证只有发起者类可以访问备忘录类
*/
@Data
class Memento {
private String state = "从备份对象恢复原始对象";
private String id;
private String name;
private String phone;
public Memento() {
}
public Memento(String id, String name, String phone) {
this.id = id;
this.name = name;
this.phone = phone;
}
}
/**
* 看护人角色
* -- 获取和保存备忘录对象
*/
@Data
public class Caretaker {
private Memento memento;
}
public class Client {
public static void main(String[] args) {
// 创建发起人对象
Originator o1 = new Originator();
o1.setId("1");
o1.setName("123");
o1.setPhone("123456789");
// 创建看护人对象
Caretaker caretaker = new Caretaker();
caretaker.setMemento(o1.createMemento()); // 备份操作
// 修改操作
o1.setName("upadte");
System.out.println(o1);
// 恢复
o1.restoreMemento(caretaker.getMemento());
System.out.println(o1);
}
}
九、命令模式
1、定义
核心思想:将命令封装成一个对象,把这个对象当参数传递。
将命令封装为一个对象,这样可以使用不同的请求参数化其他对象,并且能够支持命令的排队执行、记录日志、撤销等功能。
2、结构
3、优缺点
优点 | 降低系统的耦合度; 能将调用操作的对象和实现操作的对象解耦; |
增加或者删除命令非常方便; 满足【开闭原则】; | |
可以实现宏命令; 命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令; | |
缺点 | 可能导致系统有过多的具体命令类; |
系统结构更加复杂; |
4、使用场景
需要将调用者和实现者分离,使调用者和接收者不直接交互。
需要在不同时间指定请求,将请求排队和执行。
需要支持命令的Undo操作和Redo操作。
5、示例代码
/**
* 抽象命令角色
*/
public interface Command {
void execute(); // 统一的执行方法
}
/**
* 具体命令角色
*/
public class OrderCommand implements Command {
// 维护一个接收者对象
private final Chef receiver;
private final Order order;
public OrderCommand(Chef receiver, Order order) {
this.receiver = receiver;
this.order = order;
}
@Override
public void execute() {
receiver.makeFood(order); // 调用接收者对象的方法
System.out.println(order.getDiningTable() + "桌已上菜");
System.out.println("=========================\n");
}
}
/**
* 接收者角色
* -- 实现具体功能
*/
public class Chef {
public void makeFood(Order order){
System.out.println("制作" + order.getDiningTable() + "号桌的食物");
Map<String, Integer> foodMenu = order.getFoodMenu();
for (String key : foodMenu.keySet()) {
Integer value = foodMenu.get(key);
System.out.println(value + "份" + key);
}
System.out.println("=========================");
}
}
/**
* 调用者角色
* -- invoker
*/
public class Waiter {
// 可以持有多个命令对象
private ArrayList<Command> commands = new ArrayList<>();
public void addCommand(Command command) {
this.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 order1 = new Order();
Order order2 = new Order();
// 创建接收者
Chef chef = new Chef();
// 组装命令对象
OrderCommand command_1 = new OrderCommand(chef, order1);
OrderCommand command_2 = new OrderCommand(chef, order2);
// 创建调用者
Waiter waiter = new Waiter();
waiter.addCommand(command_1);
waiter.addCommand(command_2);
// 调用者调用抽象命令的方法
waiter.orderUp();
}
}
十、解释器模式
1、定义
用来构建一种特定“语言”的语法规则。
用于定义语言的语法规则表示,并且提供解释器处理句子中的语法。
定义、表示、解释。
2、结构
3、优缺点
优点 | 易于改变和扩展文法; 每一个文法规则都可以表示为一个类,所以可以通过继承等机制改变或者扩展文法; |
实现文法比较容易; 在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码不会特别复杂; | |
增加新的解释表达式比较方便; 增加新的解释表达式,只需要对应增加一个新的表达式类即可,原有表达式不用修改, 符合【开闭原则】; | |
缺点 | 对于复杂文法难以维护; 一条规则至少定义一个类,如果有太多文法规则,就会使类的个数急剧增加,导致系统的维护变困难; |
执行效率低; 在解释器模式中大量的使用循环和递归调用,所有复杂的句子执行起来的过程非常繁琐; |
4、使用场景
当语言的文法比较简单,并且执行效率不是关键问题。
当问题重复出现,并且可以用一种简单的语言来进行表达。
当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候。
5、示例代码
/**
* 表达式解释器类
*/
public class ExpressionInterpreter {
private final Deque<Expression> deque = new LinkedList<>();
public long interpret(String expression) throws Exception {
String[] split = expression.split(" ");
int length = split.length;
// 整理数字
for (int i = 0; i < (length + 1) /2 ; ++i){
deque.addLast(new NumExpression(split[i]));
}
// 整理符号
for (int i = (length + 1) / 2 ; i < length; ++i){
String operator = split[i];
// 符号必须是 + - * /,否则异常
boolean isValid = "+".equals(operator)
|| "-".equals(operator)
|| "*".equals(operator)
|| "/".equals(operator);
if (!isValid){
throw new Exception("无效表达式!" + expression);
}
Expression exp_1 = deque.pollFirst();
Expression exp_2 = deque.pollFirst();
Expression result = null;
if ("+".equals(operator)){
result = new PluExpression(exp_1,exp_2);
}else if ("-".equals(operator)){
result = new SubExpression(exp_1,exp_2);
}else if ("*".equals(operator)){
result = new MulExpression(exp_1,exp_2);
}else if ("/".equals(operator)){
result = new DivExpression(exp_1,exp_2);
}
long num = result.interpret();
deque.addFirst(new NumExpression(num));
}
// 注意:最终结果存储在集合中
if (deque.size() != 1){
throw new Exception("无效表达式!" + expression);
}
return deque.getFirst().interpret();
}
}
/**
* 抽象表达式角色
*/
public interface Expression {
long interpret();
}
/**
* 具体表达式角色 - 乘法运算
*/
public class MulExpression implements Expression {
private final Expression exp_1;
private final Expression exp_2;
public MulExpression(Expression exp_1, Expression exp_2) {
this.exp_1 = exp_1;
this.exp_2 = exp_2;
}
@Override
public long interpret() {
return exp_1.interpret() * exp_2.interpret();
}
}
/**
* 给解释器传一个表达式
*/
public class Client {
public static void main(String[] args) throws Exception {
ExpressionInterpreter interpreter = new ExpressionInterpreter();
long interpret = interpreter.interpret("6 2 3 2 4 / - + *");
System.out.println(interpret);
}
}
十一、中介者模式
1、定义
核心:中转引用、协调。
定义一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给中介对象,避免对象之间直接交互。
举例:飞机和塔台交互,飞机和飞机不交互。
2、结构
3、优缺点
优点 | 简化了对象之间的交互。 用中介者代替了对象之间的直接交互,更好理解。 由多对多改成了一对多,由网状结构改成了星形结构。 |
将各个同事对象进行了解耦。 可以独立地改变/复用每一个同事对象或中介者对象。 增加新的同事类和中介这类很方便,符合【开闭原则】。 | |
减少了子类生成。 中介者将原本分布于多个对象的行为集中在了一起,改变这些行为只需要生成中介者的子类。 | |
缺点 | 具体中介者类中包含了大量同事类之间的交互细节,可能导致中介这类变得很复杂。 |
4、使用场景
系统中对象之间存在复杂的引用关系,系统结构混乱难以理解。
一个对象引用了其他的很多对象,并且直接交互,导致这个对象难以复用。
想用通过一个中间类来封装多个类中的行为,又不想生成太多的子类,可以使用中介者类来实现。
5、示例代码
/**
* 抽象中介者
*/
public interface Mediator {
// 处理同事对象的注册与转发同事对象信息
void apply(String key);
}
/**
* 具体中介者
*/
public class ConcreteMediator implements Mediator {
@Override
public void apply(String key) {
System.out.println("最终中介者执行的操作:" + key);
}
}
/**
* 抽象同事类
*/
public abstract class Colleague {
// 维护一个中介对象
private final Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
// 获取中介对象
public Mediator getMediator() {
return mediator;
}
// 同事间进行交互的抽象方法
public abstract void exec(String key);
}
/**
* 具体同事类
*/
public class ConcreteColleague_A extends Colleague {
public ConcreteColleague_A(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("在A同事中 - 调用 - 中介者执行");
getMediator().apply(key);
}
}
public class Client {
public static void main(String[] args) {
// 创建中介者
Mediator mediator = new ConcreteMediator();
// 创建同事对象
ConcreteColleague_A colleague_a = new ConcreteColleague_A(mediator);
ConcreteColleague_B colleague_b = new ConcreteColleague_B(mediator);
colleague_a.exec("key_A");
colleague_b.exec("key_B");
}
}