当前位置: 首页 > article >正文

设计模式-3 行为型模式

一、观察者模式

1、定义

定义对象之间的一对多的依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。

描述复杂的流程控制

描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务

它涉及算法与对象间职责的分配;

类行为模式

采用继承机制在类之间分配行为

对象行为模式

采用组合在对象间分配行为

优势

由于组合关系或聚合关系比继承关系耦合度低,满足”合成复用原则“;

所以对象行为模式更灵活;

2、结构

3、优缺点

优点

降低了观察者和目标类的耦合关系;

两者之间是抽象耦合关系;

目标类发送通知,所有观察者都能收到消息;

可以实现广播机制;

缺点

如果观察者非常多,那么所有的观察者收到消息会耗时;

如果观察者循环依赖的话,那么目标类发送通知会使观察者循环调用,导致系统崩溃;

4、使用场景

当一个对象状态的改变需要改变其他对象时;比如:库存变化时,要通知商品页面;

一个对象只想发送通知,不用管谁能收到;比如:朋友圈,微博;

需要创建一种链式触发机制;A改变B,B改变C,...

5、示例代码

/*** 抽象观察者*/
public interface Observer {// 为不同的观察者更新行为定义一个相同的接口// 不同的观察者对该方法由不同的实现public void update();
}
/*** 具体观察者 1*/
public class ConcreteObserver_1 implements Observer{@Overridepublic void update() {System.out.println("1  ==  得到通知,更新状态");}
}/*** 具体观察者 2*/
public class ConcreteObserver_2 implements Observer{@Overridepublic 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<>();// 注册,向观察者集合添加一个观察者@Overridepublic void attach(Observer observer) {list.add(observer);}// 注销,移除一个观察者@Overridepublic void detach(Observer observer) {list.remove(observer);}// 通知@Overridepublic 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{@Overridevoid step_3() {System.out.println("在子类A中 -> 执行步骤 3");}@Overridevoid step_4() {System.out.println("在子类A中 -> 执行步骤 4");}
}
/*** 实现类B*/
public class ConcreteClassB extends AbstractTemplate{@Overridevoid step_3() {System.out.println("在子类B中 -> 执行步骤 3");}@Overridevoid 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{@Overridevoid algorithm() {System.out.println("具体策略A");}}/*** 具体策略B*/
public class ConcreteStrategyB extends Strategy{@Overridevoid 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic void handle(Context context) {System.out.println("进入状态模式A");context.setCurrentState(this);}@Overridepublic String toString() {return "当前状态:A";}
}
/*** 具体状态类 B*/
public class ConcreteState_B implements State {@Overridepublic void handle(Context context) {System.out.println("进入状态模式B");context.setCurrentState(this);}@Overridepublic String toString() {return "当前状态:B";}
}
/*** 上下文类*/
@Data
public class Context {// 维护一个对状态对象的引用private State currentState = null;public Context() {}public Context(State currentState) {this.currentState = currentState;}@Overridepublic String toString() {return "Context{" +"currentState=" + currentState +'}';}
}
public class Client {public static void main(String[] args) {// 创建模式AConcreteState_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;}@Overridepublic boolean hasNext() {return cursor != list.size();}@Overridepublic void next() {cursor++;}@Overridepublic 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);}@Overridepublic 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);}@Overridepublic 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));}}@Overridepublic void visit(Wine wine) {System.out.println("酒类:" + wine.getName() + "不过期,无折扣!");System.out.println("酒水价格:" + NumberFormat.getCurrencyInstance().format(wine.getPrice()));}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic 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 {@Overridepublic 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);}@Overridepublic 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");}
}

相关文章:

设计模式-3 行为型模式

一、观察者模式 1、定义 定义对象之间的一对多的依赖关系&#xff0c;这样当一个对象改变状态时&#xff0c;它的所有依赖项都会自动得到通知和更新。 描述复杂的流程控制 描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务 它涉及算法与对象间职责…...

qt 双缓冲案例对比

双缓冲 1.双缓冲原理 单缓冲&#xff1a;在paintEvent中直接绘制到屏幕&#xff0c;绘制过程被用户看到 双缓冲&#xff1a;先在redrawBuffer绘制到缓冲区&#xff0c;然后一次性显示完整结果 代码结构 单缓冲&#xff1a;所有绘制逻辑在paintEvent中 双缓冲&#xff1a;绘制…...

2025年全国I卷数学压轴题解答

第19题第3问: b b b 使得存在 t t t, 对于任意的 x x x, 5 cos ⁡ x − cos ⁡ ( 5 x t ) < b 5\cos x-\cos(5xt)<b 5cosx−cos(5xt)<b, 求 b b b 的最小值. 解: b b b 的最小值 b m i n min ⁡ t max ⁡ x g ( x , t ) b_{min}\min_{t} \max_{x} g(x,t) bmi…...

JS设计模式(5): 发布订阅模式

解锁JavaScript发布订阅模式&#xff1a;让代码沟通更优雅 在JavaScript的世界里&#xff0c;我们常常会遇到这样的场景&#xff1a;多个模块之间需要相互通信&#xff0c;但是又不想让它们产生过于紧密的耦合。这时候&#xff0c;发布订阅模式就像一位优雅的信使&#xff0c;…...

实现p2p的webrtc-srs版本

1. 基本知识 1.1 webrtc 一、WebRTC的本质&#xff1a;实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力&#xff0c;二者在架构设计和功能定位上高度相似&#xff1a; 分层协议栈架构 Linux网络协议栈&#xff1a;从底层物理层到应用层&#xff08;如…...

Android多媒体——音/视频数据播放(十八)

在媒体数据完成解码并准备好之后,播放流程便进入了最终的呈现阶段。为了确保音视频内容能够顺利输出,系统需要首先对相应的播放设备进行初始化。只有在设备初始化成功后,才能真正开始音视频的同步渲染与播放。这一过程不仅影响播放的启动速度,也直接关系到播放的稳定性和用…...

第2篇:BLE 广播与扫描机制详解

本文是《BLE 协议从入门到专家》专栏第二篇,专注于解析 BLE 广播(Advertising)与扫描(Scanning)机制。我们将从协议层结构、广播包格式、设备发现流程、控制器行为、开发者 API、广播冲突与多设备调度等方面,全面拆解这一 BLE 最基础也是最关键的通信机制。 一、什么是 B…...

开源 vGPU 方案:HAMi,实现细粒度 GPU 切分

本文主要分享一个开源的 GPU 虚拟化方案&#xff1a;HAMi&#xff0c;包括如何安装、配置以及使用。 相比于上一篇分享的 TimeSlicing 方案&#xff0c;HAMi 除了 GPU 共享之外还可以实现 GPU core、memory 得限制&#xff0c;保证共享同一 GPU 的各个 Pod 都能拿到足够的资源。…...

EC2安装WebRTC sdk-c环境、构建、编译

1、登录新的ec2实例&#xff0c;证书可以跟之前的实例用一个&#xff1a; ssh -v -i ~/Documents/cert/qa.pem ec2-user70.xxx.165.xxx 2、按照sdk-c demo中readme的描述开始安装环境&#xff1a; https://github.com/awslabs/amazon-kinesis-video-streams-webrtc-sdk-c 2…...

盲盒一番赏小程序:引领盲盒新潮流

在盲盒市场日益火爆的今天&#xff0c;如何才能在众多盲盒产品中脱颖而出&#xff1f;盲盒一番赏小程序给出了答案&#xff0c;它以创新的玩法和优质的服务&#xff0c;引领着盲盒新潮流。 一番赏小程序的最大特色在于其独特的赏品分级制度。赏品分为多个等级&#xff0c;从普…...

边缘计算设备全解析:边缘盒子在各大行业的落地应用场景

随着工业物联网、AI、5G的发展&#xff0c;数据量呈爆炸式增长。但你有没有想过&#xff0c;我们生成的数据&#xff0c;真的都要发回云端处理吗&#xff1f;其实不一定。特别是在一些对响应时间、网络带宽、数据隐私要求高的行业里&#xff0c;边缘计算开始“火”了起来&#…...

Linux实现线程同步的方式有哪些?

什么是线程同步&#xff1f; 想象一下超市收银台&#xff1a;如果所有顾客&#xff08;线程&#xff09;同时挤向同一个收银台&#xff08;共享资源&#xff09;&#xff0c;场面会一片混乱。线程同步就是给顾客们发"排队号码牌"&#xff0c;确保&#xff1a; 有序访…...

python打卡day47

昨天代码中注意力热图的部分顺移至今天 知识点回顾&#xff1a; 热力图 作业&#xff1a;对比不同卷积层热图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import D…...

python学习day39

图像数据与显存 知识点回顾 1.图像数据的格式&#xff1a;灰度和彩色数据 2.模型的定义 3.显存占用的4种地方 a.模型参数梯度参数 b.优化器参数 c.数据批量所占显存 d.神经元输出中间状态 4.batchisize和训练的关系 import torch import torchvision import torch.nn as nn imp…...

Ansible+Zabbix-agent2快速实现对多主机监控

ansible Ansible 是一款开源的自动化工具&#xff0c;用于配置管理&#xff08;Configuration Management&#xff09;、应用部署&#xff08;Application Deployment&#xff09;、任务自动化&#xff08;Task Automation&#xff09;和编排&#xff08;Orchestration&#xf…...

年度峰会上,抖音依靠人工智能和搜索功能吸引广告主

上周早些时候举行的第五届年度TikTok World产品峰会上&#xff0c;TikTok推出了一系列旨在增强该应用对广告主吸引力的功能。 新产品列表的首位是TikTok Market Scope&#xff0c;这是一个全新的分析平台&#xff0c;为广告主提供整个考虑漏斗的全面视图&#xff0c;使他们能够…...

Vuex:Vue.js 应用程序的状态管理模式

什么是Vuex&#xff1f; Vuex 是专门为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 在大型单页应用中&#xff0c;当多个组件共享状态时&#xff0c;简单的单向数据流…...

【中间件】Web服务、消息队列、缓存与微服务治理:Nginx、Kafka、Redis、Nacos 详解

Nginx 是什么&#xff1a;高性能的HTTP和反向代理Web服务器。怎么用&#xff1a;通过配置文件定义代理规则、负载均衡、静态资源服务等。为什么用&#xff1a;提升Web服务性能、高并发处理、负载均衡和反向代理。优缺点&#xff1a;轻量高效&#xff0c;但动态处理能力较弱&am…...

如何使用CodeRider插件在IDEA中生成代码

一、环境搭建与插件安装 1.1 环境准备 名称要求说明操作系统Windows 11JetBrains IDEIntelliJ IDEA 2025.1.1.1 (Community Edition)硬件配置推荐16GB内存50GB磁盘空间 1.2 插件安装流程 步骤1&#xff1a;市场安装 打开IDEA&#xff0c;进入File → Settings → Plugins搜…...

电脑定时关机工具推荐

软件介绍 本文介绍一款轻量级的电脑自动关机工具&#xff0c;无需安装&#xff0c;使用简单&#xff0c;可满足定时关机需求。 工具简介 这款关机助手是一款无需安装的小型软件&#xff0c;文件体积仅60KB&#xff0c;下载后可直接运行&#xff0c;无需复杂配置。 使用…...

Ubuntu 可执行程序自启动方法

使用 autostart&#xff08;适用于桌面环境&#xff09; 适用于 GNOME/KDE 桌面环境&#xff08;如 Ubuntu 图形界面&#xff09; 1. 创建 .desktop 文件 sudo vi ~/.config/autostart/my_laser.desktop[Desktop Entry] TypeApplication NameMy Laser Program Execbash -c &…...

Springboot多数据源配置实践

Springboot多数据源配置实践 基本配置文件数据库配置Mapper包Model包Service包中业务代码Mapper XML文件在某些复杂的业务场景中,我们可能需要使用多个数据库来存储和管理不同类型的数据,而不是仅仅依赖于单一数据库。本技术文档将详细介绍如何在 Spring Boot 项目中进行多数…...

第6章:Neo4j数据导入与导出

在实际应用中&#xff0c;数据的导入与导出是使用Neo4j的重要环节。无论是初始数据加载、系统迁移还是数据备份&#xff0c;都需要高效可靠的数据传输机制。本章将详细介绍Neo4j中的各种数据导入与导出方法&#xff0c;帮助读者掌握不同场景下的最佳实践。 6.1 数据导入策略 …...

uni-app学习笔记二十三--交互反馈showToast用法

showToast部分文档位于uniapp官网-->API-->界面&#xff1a;uni.showToast(OBJECT) | uni-app官网 uni.showToast(OBJECT) 用于显示消息提示框 OBJECT参数说明 参数类型必填说明平台差异说明titleString是提示的内容&#xff0c;长度与 icon 取值有关。iconString否图…...

Go爬虫开发学习记录

Go爬虫开发学习记录 基础篇&#xff1a;使用net/http库 Go的标准库net/http提供了完善的HTTP客户端功能&#xff0c;是构建爬虫的基石&#xff1a; package mainimport ("fmt""io""net/http" )func fetchPage(url string) string {// 创建自定…...

前端异步编程全场景解读

前端异步编程是现代Web开发的核心&#xff0c;它解决了浏览器单线程执行带来的UI阻塞问题。以下从多个维度进行深度解析&#xff1a; 一、异步编程的核心概念 JavaScript的执行环境是单线程的&#xff0c;这意味着在同一时间只能执行一个任务。为了不阻塞主线程&#xff0c;J…...

分布式光纤声振传感技术原理与瑞利散射机制解析

分布式光纤传感技术&#xff08;Distributed Fiber Optic Sensing&#xff0c;简称DFOS&#xff09;作为近年来迅速发展的新型感知手段&#xff0c;已广泛应用于边界安防、油气管道监测、结构健康诊断、地震探测等领域。其子类技术——分布式光纤声振传感&#xff08;Distribut…...

RocketMQ 客户端负载均衡机制详解及最佳实践

延伸阅读&#xff1a;&#x1f50d;「RocketMQ 中文社区」 持续更新源码解析/最佳实践&#xff0c;提供 RocketMQ 专家 AI 答疑服务 前言 本文介绍 RocketMQ 负载均衡机制&#xff0c;主要涉及负载均衡发生的时机、客户端负载均衡对消费的影响&#xff08;消息堆积/消费毛刺等…...

Q1起重机指挥理论备考要点分析

Q1起重机指挥理论备考要点分析 一、考试重点内容概述 Q1起重机指挥理论考试主要包含三大核心模块&#xff1a;安全技术知识&#xff08;占40%&#xff09;、指挥信号规范&#xff08;占30%&#xff09;和法规标准&#xff08;占30%&#xff09;。考试采用百分制&#xff0c;8…...

c++算法学习3——深度优先搜索

一、深度优先搜索的核心概念 DFS算法是一种通过递归或栈实现的"一条路走到底"的搜索策略&#xff0c;其核心思想是&#xff1a; 深度优先&#xff1a;从起点出发&#xff0c;选择一个方向探索到底&#xff0c;直到无路可走 回溯机制&#xff1a;遇到死路时返回最近…...