行为型模式 · 设计模式

Interperter-解释器

why

对象化每条语法规则, 方便组合与扩展

  1. 有一门小语言或表达式要在程序里被解析和执行(例如数学表达式、过滤规则、正则、路径遍历逻辑)。

  2. 如果你把所有解析和计算写在一堆 if/else 或 switch 里,代码会很快变得不可维护、不可扩展。

角色

  • 抽象表达式 (Expression/Node) : 定义interpret(context) or evaluate 接口
  • 终结表达式 (TerminalExpression): 叶子节点 (常数、变量、字面量)
  • 非终结表达式 (NonterminalExpression): 组合节点 (加法、乘法、and、or、重复等), 内部持有子表达式引用并组合他们的结果.

本质映射

  • 语言句子 -> 语法树根节点
  • 每个语法构造 -> 一个类(对象)
  • interpret() -> 节点的解释/匹配/求值动作

How

  1. 词法分析 (Tokenizer / Lexer) 把文本拆成一个个token (数字、标识符、运算符、括号)

  2. 语法分析 (Parser) 用递归下降 / LL / LR / 手写栈等把token组织成树结构

    Pasted image 20260416211822
  3. 解释/求值 (Interpreter) 从根节点调用evaluate(context) or match(), 节点递归调用子节点并组合结果 例: +节点会先让左子节点返回1, 右子节点返回6 (2*3), 最后返回7

语法简单, 但是用了递归, 所以执行效率不高

Command-命令

why

把请求封装成对象, 让请求 和 执行者解耦, 比如厨师和菜品

角色

  • Command (命令接口) : 声明执行命令的接口 execute()

  • ConcreteCommand (具体命令) : 实现命令接口, 把请求绑定接收者

  • Receiver (接收者) : 真正执行请求的对象

  • Invoker (调用者) : 持有命令对象, 调用execute()

  • Client (客户端) : 创建具体命令并设置接收者

JDK源码

java.lang.Runnable + ExecutorService

代码块JAVA · 3 行收起展开
Runnable task  = ()->System.outprintln("执行任务"); 
ExecutorService executor = Executors.new FixedThreadPool(1);
executor.execute(task);
  • Command→ Runnable对象,封装任务
  • Receiver→实际执行任务的线程
  • Invoker → ExecutorService, 调用 Runnable.run()
  • Client→创建任务对象,提交给线程池

java.util.TimerTask + Timer

代码块JAVA · 8 行收起展开
TimerTask task = new TimerTask(){
	@Override
	public void run(){
		"定时任务执行".sout;
	}
};

new Timer().schedule(task, 1000);
  • Command → TimerTask 对象
  • Receiver→ Timer线程执行逻辑
  • Invoker→ Timer 调度器
  • Client→ 创建TimerTask并提交

优缺点

优点: 降低耦合度, 增删命令方便, 可以实现宏命令

缺点: 某些系统会有过多的具体命令类

场景

  • 请求调用者和请求接收者解耦
  • 系统需要不同时间指定请求, 将请求排队和执行请求
  • 系统需要支持命令的撤销和恢复操作 (结合备忘录模式)

DutyLink-责任链

代码块JAVA · 27 行收起展开
public final class ApplicationFilterChain implements FilterChain{
	private final Filter[] filters; //过滤器s
	private int pos=0;
	private final Servlet servlet;
	
	
	public ApplicationFilterChain(Filter[] filters, Servlet servlet){
		this.filters = filters;
		this.servlet = servlet;
	}
	
	
	@Override
	public void doFilter(ServletRequest request, ServletResponse responses) throws IOException, ServletException{
		//filters还没执行完
		if(pos<filters.length){
			Filter current = filters[pos++];//移动到下一个
			
			//把同一个chain传给当前filter, 当前filter可以选择chain.doFilter() 继续
			current.doFilter(request, response, this);
			return;
		}
		
		//全部filter执行完, 调用目标servlet
		servlet.service(request, response);
	}
}

why

强调多个策略的共性和关联性

假如我们要做一个请求处理系统, 请求会经过多个”处理器”, 比如:

  • 认证处理器 (检查用户是否登录)
  • 日志处理器 (记录请求日志)
  • 业务处理器 (真正执行逻辑)

这些处理器可能要有序串联, 并且动态组合

核心

  • 抽象处理者: 抽取各个层次的共性为抽象方法, 并且提供连接各层的函数 把”处理请求”定义成一个抽象方法, 比如handleRequest(), 并且持有”下一个处理者”的引用next

  • 具体处理者: 抽象处理者的子实现类 每个具体处理者只负责自己的那一环, (比如权限校验、日志、限流等) 干完自己的活 决定是否调用 next.handleRequest()

  • 模板方法 or final 链接方法 抽象类往往会提供一个handle()process()的模板方法, 它控制整个流程, 而子类只需要实现”自己该做什么”那部分逻辑

优缺点

  • 缺点: - 连接需要手动 - 对较长的职责链, 请求的处理涉及多个处理对象, 性能有影响 - 职责链建立的合理性要靠客户端来保证, 增加了客户端的复杂性, 可能由于职责链的错误设置而导致系统出错

  • 优点:

    • 降低对象之间的耦合度
    • 增加系统的可拓展性
    • 简化了对象之间的连接
    • 有next指针, 避免了众多if or if else语句

Iterator-迭代器

代码块JAVA · 24 行收起展开
/*
List: 抽象聚合类
ArrayList: 具体聚合类
Iterator: 抽象迭代器
list.iterator(): 返回的是实现了`Iterator`接口的具体迭代器对象
*/

public class ArrayList<E> extends AbstactList<E> 
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	public Iterator<E> iterator(){
		return new Itr();
	}
	
	private class Itr implements Iterator<E>{
		int cursor;
		int lastRet = -1;
		int expectedModCount = modCount;
		
		Itr(){}
		//判断是否还有元素
		public boolean hasNext(){
		}
	}
}

why

用来顺序访问聚合对象的数据, 而不暴露聚合对象的内部表示

角色

  • 抽象聚合 (Aggregate): 定义存储、添加、删除聚合元素以及创建迭代器的接口

  • 具体聚合 (ConcreteAggregate): 实现抽象聚合类, 返回一个具体迭代器的实例

  • 抽象迭代器 (Iterator): 定义访问和遍历聚合元素的接口, 通常包含 hasNext()、next()等方法

  • 具体迭代器 (Concretelterator): 实现抽象迭代器接口中所定义的方法, 完成对聚合对象的遍历, 记录遍历的当前位置

优缺点

优点:

  • 由于引入了迭代器, 在原有的聚合对象中不需要再自行提供数据遍历等方法, 简化了聚合类

  • 可以定义多种遍历方式

缺点:

  • 增加了类的个数, 系统复杂性upup

应用

当我们使用JAVA开发的时候, 想使用迭代器模式 只要让我们自己定义的容器类实现java.util.Iterable 并实现其中的iterator()方法使其返回一个java.util.Iterator的实现类即可

Mediator-中介

代码块JAVA · 74 行收起展开
//抽象中介者
abstract class Mediator{
	public abstract void send(String msg, Colleague colleague);
}

//具体中介者
class ChatRoom extends Mediator{
	@Override
	public void send(String msg, Colleague colleague){
		for(Colleague c: sender.getMediator().getColleangues()){
			if(c!=sender){
				c.notify(msg);
			}
		}
	}
	
	private List<Colleague> colleagues = new ArrayList<>();
	
	//添加中介者的客户
	public void addColleague(Colleague c){
		colleagues.add(c);
	}
	
	public List<Colleague> getColleagues(){
		return colleagues;
	}
}

//抽象同事类
abstract class Colleague{
//内置一个中介来进行操作
	protected Mediator mediator;
	public Colleague(Mediator mediator){
		this.mediator = mediator;
	}
	
	public Mediator getMediator(){
		return mediator;
	}
	
	public abstract void notify(String msg);
}

class User extends Colleague{
	private String name;
	public User(String name, Mediator mediator){
		super(mediator);
		this.name = name;
	}
	
	public void send(String msg){
		mediator.send(name+". "+msg, this);
	}
	
	@Override
	public void notify(String msg){
		(name+"收到消息"+msg) .sout;
	}
}

public class test{
	psvm(){
		ChatRoom cr= new ChatRoom();
		User u1 = new User("A", cr);
		User u2 = new User("B", cr);
		User u3 = new User("C", cr);
			
		chatRoom.addColleague(u1);
		chatRoom.addColleague(u2);
		chatRoom.addColleague(u3);
		
		u1.send("hello");
	}
}

why

用一个中介对象来封装一系列对象的交互, 使对各对象不直接引用彼此, 从而降低耦合

角色

  1. Mediator 抽象中介 定义对象之间通信的接口

  2. Concrete Mediator 具体中介 实现中介者接口, 协调各同事对象的交互 保存对各同时对象的引用

  3. Colleague 同事类 只知道和通过中介者与其他对象交互

优缺点

优点:

  • 一对多关联转变为一对一关联
  • 集中控制交互
  • 松散耦合

缺点: 当同事类太多时, 中介的职责将很大, 很难维护

使用场景

  • 系统中对象之间存在复杂的引用关系
  • 创建一个运行于多个类之间的对象 & 又不想生成新的子类时

Memento-备忘录

代码块JAVA · 67 行收起展开
//发起人, 要被保存状态的对象
class Originator{
	private String state;
	
	public void setState(String state){
		this.state = state;
	}
		
	public void show(){
		sout;
	}
	
	//创建
	public Memento save(){
		return new Memento(state);
	}
	
	//恢复备忘录
	public void restore(Memento memento){
		this.state = memento.getState();
	}
}

//备忘录 (存状态)
class Memento{
	private final String state;
	
	public Memento(String state){
		this.state = state;
	}
	
	public String getState(){
		return state;
	}
}

//管理者
class Caretaker{
	private List<Memento> history = new ArrayList<>();
	
	public void add(Memento m){
		history.add(m);
	}

	public Memento get(int index){
		return history.get(index);
	}
}

public class test{
	psvm{
		Originator o = new Originator();
		Caretaker c = new Caretaker();
		
		o.setState("1");
		c.add(o.save());
		
		o.setState("2");
		c.add(o.save());
		
		o.setState("3");
		o.show();
		
		o.restore(c.get(0));
		o.show();
	}
}

why

实现undo

角色

  1. 发起人: 记录当前时刻的内部状态信息 能创建和恢复备忘录数据, 能访问备忘录里的所有信息

  2. 备忘录: 负责存储发起人的内部状态, 在需要的时候提供

  3. 管理者: 对备忘录进行管理, 提供保存与获取备忘录的功能, 但是不能访问和修改

  • Originator:被保存的人
  • Memento:状态快照
  • Caretaker:帮你存历史

备忘录两个等效接口

  • 窄接口: 管理者对象(or 其他任何非发起人对象)看到的是备忘录的窄接口, 这个窄接口只允许他把备忘录对象传给其他对象

  • 宽接口: 发起人对象可以看到一个宽接口, 允许它读取所有的数据, 以便根据数据恢复这个发起人对象的内部状态

白备忘录

此类可以让history管理者进行备忘录内容的修改, 应只允许editor操作备忘录

黑备忘录

备忘录的子实现类定义成发起者的内部私有类, 只有发起者自己可以直接访问,

对外提供方法用memento接口接收, 然后将其强转为我们的memento内部类

对外暴露的是接口, 管理者拿不到具体实现就拿不到数据

PatternMethod-模板方法

代码块JAVA · 21 行收起展开
public abstract class AbstractList<E> implements List<E> {

    public boolean add(E e) {
        add(size(), e);  // 模板方法的一部分
        return true;
    }

    public abstract void add(int index, E element); // 留给子类实现

    public abstract E get(int index);

    public abstract int size();
}

protected void service(HttpServletRequest req, HttpServletResponse resp) {
    if (req.getMethod().equals("GET")) {
        doGet(req, resp);
    } else if (...) {
        doPost(req, resp);
    }
}

why

  1. 控制反转 通常你认为子类/调用者掌控执行:谁调用谁就能控制。 而模板方法模式反过来:父类掌控流程,子类只提供细节 所以叫“反向控制”----------控制权从子类/调用者反转到了父类

  2. 定义框架 定义算法的骨架, 将具体步骤的实现延迟到子类

  • 父类写好流程模板
  • 子类只负责实现可变部分 ---> 防止子类乱改流程, 同时让其自由实现细节

角色

  • 抽象类: 给出算法的框架和轮廓, 由一个模板方法和若干个基本方法构成
    • 模板方法: 定义算法的骨架, 按某种顺序调用其包含的基本方法

    • 基本方法: 实现算法各个步骤的方法, 是模板方法的组成部分, 由其具体子类实现

      • 抽象方法: 由抽象类声明, 由具体子类实现
      • 具体方法: 一个具体方法由一个抽象类or具体类声明并实现
      • 钩子方法: 在抽象类已实现, 包括 判断的逻辑方法 & 子类重写的空方法, 一般钩子方法用于判断的逻辑方法, 这类方法名一般为isXxxx, 返回boolean类型
  • 具体子类: 实现抽象类中所定义的抽象方法和钩子方法

优缺点

优点:

  • 提高代码复用性 将相同部分的代码放在抽象的父类中, 而将不同的代码放入不同的子类中

  • 实现反向控制 通过父类调用子类的操作, 通过子类的具体实现拓展不同的行为, 实现了反向控制

缺点:

  • 每个不同的实现都需定义一个子类, 这会导致类个数增加, 系统更庞大
  • 父类中的抽象方法由子类实现, 子类执行结果会影响父类的结果, 这导致一种反向的控制结构, 代码阅读难度大

使用场景

  • 算法的步骤固定, 但个别部分需要改变, 此时将易变部分抽象出来, 供子类实现 , 需要通过子类来决定父类中某个步骤是否执行, 实现子类对父类的反向控制

典型案例

AQS

代码块JAVA · 11 行收起展开
//模板方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

//钩子方法 ---> 判断的逻辑方法 & 子类重写的空方法
protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

PubSub-观察者

代码块JAVA · 26 行收起展开
class Subscriber {
    public void wake(String msg) {
        System.out.println(msg);
    }
}

class Publisher {
    List<Subscriber> subs = new ArrayList<>();

    String msg;

    public void update(String msg) {
        this.msg = msg;
        notifySubscribers();
    }

    public void notifySubscribers() {
        for (Subscriber sub : subs) {
            sub.wake(msg);
        }
    }

    public void addSubscriber(Subscriber sub) {
        subs.add(sub);
    }
}

why

  • 解耦: 被观察者不需要知道观察者具体是谁, 只要提供注册 / 通知机制即可

  • 响应式更新: 状态改变后自动通知相关对象, 而不是调用者每次手动去刷新

  • 易扩展: 新增观察者不影响被观察者的代码

how

通过为publisher内置suber集合, 并在update后调用notify通知所有suber

优缺点

优点:

  • 降低了目标和观察者之间的耦合关系, 两者之间是 抽象耦合关系
  • 被观察者发送通知, 所有注册的观察者都会收到信息 (可实现广播机制)

缺点:

  • for循环通知大量观察者会耗时
  • 被观察者有循环依赖的话, 那么被观察者发送通知会使观察者循环调用, 导致系统崩溃
代码块JAVA · 3 行收起展开
A → 通知 → B  
B → 通知 → A
//A → B → A → B → A → B → … 无限循环

JDK

代码块JAVA · 51 行收起展开
interface Observer {
    void update(String msg);
}

class ConcreteObserver implements Observer {
    private final String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String msg) {
        System.out.println(name + " 收到: " + msg);
    }
}

class Subject {
    private final List<Observer> observers = new ArrayList<>();
    private String msg;

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    public void setMsg(String msg) {
        this.msg = msg;
        notifyObservers();
    }

    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(msg);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Subject subject = new Subject();

        subject.addObserver(new ConcreteObserver("A"));
        subject.addObserver(new ConcreteObserver("B"));

        subject.setMsg("hello");
    }
}
代码块JAVA · 37 行收起展开
public class Main {
    public static void main(String[] args) throws Exception {
        SubmissionPublisher<String> publisher = new SubmissionPublisher<>();

        Flow.Subscriber<String> subscriber = new Flow.Subscriber<>() {
            private Flow.Subscription subscription;

            @Override
            public void onSubscribe(Flow.Subscription subscription) {
                this.subscription = subscription;
                subscription.request(1);
            }

            @Override
            public void onNext(String item) {
                System.out.println("收到: " + item);
                subscription.request(1);
            }

            @Override
            public void onError(Throwable throwable) {
                throwable.printStackTrace();
            }

            @Override
            public void onComplete() {
                System.out.println("结束");
            }
        };

        publisher.subscribe(subscriber);
        publisher.submit("hello");
        publisher.close();

        Thread.sleep(500);
    }
}

State-状态

why

写订单状态, 有 新建—> 已支付—> 已发货—> 已完成

如果你写在一个类里, 大概是这样

代码块JAVA · 16 行收起展开
switch(state){
	case NEW:
	//...
	break;
	case PAID:
	//...
	break;
}

if (state == "未支付") {
    pay();
} else if (state == "已支付") {
    ship();
} else if (state == "已发货") {
    receive();
}

如果后续变化就要改状态和逻辑, 堆积if-else

大致流程

对象行为被状态对象承包, Context只负责委托和切换状态

  1. Context 初始化

    • 内置所有状态对象(OpenState, CloseState), 并且把currentState赋值为初始值
    • 初始化时 setState(StopState), 同时StopState的context赋值Context本身, 建立双向联系
  2. 方法调用

    • 对外调用的时Context的方法 (open, close, …), 但Context只是把调用委托给currentState
    • 实际执行的是currentState指向的具体状态对象的方法 (多态动态绑定)
  3. 状态切换

    • 每个状态对象的方法里可能会修改 ContextcurrentState
    • 下一次调用Context的方法时, 真正执行的其实是新的状态对象的方法
  4. 方法执行+状态复位

    • 在方法内部根据业务逻辑完成动作, 有必要时可以重置or却换
    • 整个流程就像对象行为随状态动态切换, 而调用接口对外保持统一

code

代码块JAVA · 82 行收起展开
//状态接口
interface OrderState {
    void pay(OrderContext ctx);
    void ship(OrderContext ctx);
    void receive(OrderContext ctx);
}

//具体状态: 未支付
class UnpaidState implements OrderState {
    @Override
    public void pay(OrderContext ctx) {
        System.out.println("支付成功");
        ctx.setState(new PaidState());
    }

    @Override
    public void ship(OrderContext ctx) {
        throw new IllegalStateException("未支付,不能发货");
    }

    @Override
    public void receive(OrderContext ctx) {
        throw new IllegalStateException("未发货,不能收货");
    }
}

//具体状态: 已支付
class PaidState implements OrderState {
    @Override
    public void pay(OrderContext ctx) {
        throw new IllegalStateException("已经支付过了");
    }

    @Override
    public void ship(OrderContext ctx) {
        System.out.println("发货成功");
        ctx.setState(new ShippedState());
    }

    @Override
    public void receive(OrderContext ctx) {
        throw new IllegalStateException("未发货,不能收货");
    }
}

...

//上下文 (核心对象)
class OrderContext {
    private OrderState state;

    public OrderContext(OrderState state) {
        this.state = state;
    }

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

    public void pay() {
        state.pay(this);
    }

    public void ship() {
        state.ship(this);
    }

    public void receive() {
        state.receive(this);
    }
}

// 测试
public class Client {
    public static void main(String[] args) {
        OrderContext ctx = new OrderContext(new UnpaidState());

        ctx.request(); // 去支付
        ctx.request(); // 发货
        ctx.request(); // 收货
    }
}

Strategy-策略

why

购物车计算折扣有

  • 新用户-> 9折
  • 老用户-> 8折
  • VIP -> 7折

if/else or switch 来判断用户类型

角色

  • 策略接口
  • 策略接口的子实现类
  • 环境类(持有策略接口用以接受其子实现类)

优缺点

  • 优点 - 策略类之间都实现同一个接口, 可以自由切换 - 不改源代码可以添加一个具体策略类, 符合开闭原则 - 避免使用多重 if-else

  • 缺点

    • 客户端必须知道所有策略类, 并自行决定使用哪个
    • 策略模式将造成产生很多策略类, 应该用享元模式一定程度上减少对象的数量
    • 即使用接口接收解决了通用性问题, 但是策略并不减少

使用场景

  • 系统需要动态地在几种算法中选择一种时, 可将算法封装到策略类
  • 系统定义中各算法彼此完全独立, 且要求对客户隐藏具体算法的实现细节

code

comparator

Arrays.sort(obj, comparator<T>)

代码块JAVA · 13 行收起展开
//策略接口
public interface Comparator<T>{
	int compare(T o1,  T  o2);
}

List<Integer> list = Arrays.asList(3, 1, 2);

// 不同策略
Collections.sort(list, new Comparator<Integer>() {
    public int compare(Integer a, Integer b) {
        return a - b; // 升序
    }
});

线程池

线程池满了怎么办? 有不同策略:

  • AbortPolicy(直接抛异常)
  • CallerRunsPolicy(调用者执行)
  • DiscardPolicy(丢掉)

接口👇

代码块PLAINTEXT · 3 行收起展开
public interface RejectedExecutionHandler {  
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);  
}

👉 不同实现 = 不同拒绝策略

Visitor-访问者

Why

👉 把“操作”从对象里抽出来,放到 Visitor 里

先一句话把场景钉死: 👉 对象结构(类)不变,但要不断增加新操作

比如:

  • 员工(程序员 / 经理)结构固定
  • 但要加:算工资 / 算绩效 / 生成报表

角色

  • Element(元素):被访问的对象
  • ConcreteElement:具体元素
  • Visitor:访问者接口(定义操作)
  • ConcreteVisitor:具体操作
  • ObjectStructure:对象集合

code

代码块JAVA · 14 行收起展开
interface Visitor {
    void visit(EmployeeA a);
    void visit(EmployeeB b);
}

interface Element {
    void accept(Visitor visitor);
}

class EmployeeA implements Element {
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

分派

指的是程序在运行时决定调用哪个方法实现

  1. 动态分派 java会根据运行时的动态类型来决定调用那个speak()方法, 这叫动态分派 or 单分派
代码块JAVA · 9 行收起展开
public class Demo{
	public static void main(String[] args){
		//a的静态类型(编译时类型) 是 Animal
		//a的动态类型(运行时类型) 是 Dog
		Animal   a = new Dog();
		a.speak();
	}

}   
Pasted image 20260417125143 Pasted image 20260417125200

JDK

代码块JAVA · 19 行收起展开
//Visitor接口
public interface ElementVisitor<R, P> {
    R visitPackage(PackageElement e, P p);
    R visitType(TypeElement e, P p);
    R visitVariable(VariableElement e, P p);
    R visitExecutable(ExecutableElement e, P p);
}

//被访问者
public interface Element {
    <R, P> R accept(ElementVisitor<R, P> v, P p);
}

//具体元素
class TypeElementImpl implements Element {
    public <R, P> R accept(ElementVisitor<R, P> v, P p) {
        return v.visitType(this, p);
    }
}