状态模式(State Pattern)
概念
状态模式是一种行为型设计模式,它允许对象在内部状态改变时改变其行为,看起来就像修改了它的类一样。
通俗理解:人在不同状态下行为不同,开心时爱说话,生气时不理人。状态模式就是把这些"状态"变成对象,让状态自己决定该做什么。
核心思想
面向对象编程的精髓体现:将状态对象化。
- 传统方式:状态是简单的标识(如字符串、枚举),行为通过if-else判断
- 状态模式:状态是有行为的对象,状态对象自己决定该做什么
核心角色
- Context(上下文):维护一个具体状态对象的实例,这个实例定义当前状态
- State(抽象状态):定义一个接口,封装与Context的一个特定状态相关的行为
- ConcreteState(具体状态):每个子类实现一个与Context的一个状态相关的行为
代码对比
传统方式 - if-else地狱:
public class Order {
private String status; // "PENDING", "PAID", "SHIPPED", "COMPLETED"
public void pay() {
if (status.equals("PENDING")) {
// 支付逻辑
status = "PAID";
} else {
throw new IllegalStateException("当前状态不能支付");
}
}
public void ship() {
if (status.equals("PAID")) {
// 发货逻辑
status = "SHIPPED";
} else {
throw new IllegalStateException("当前状态不能发货");
}
}
// 每个操作都要判断状态,代码重复且难以维护
}
使用状态模式:
// 状态接口
interface OrderState {
void pay(Order order);
void ship(Order order);
}
// 待支付状态
class PendingState implements OrderState {
public void pay(Order order) {
// 支付逻辑
order.setState(new PaidState());
}
public void ship(Order order) {
throw new IllegalStateException("未支付不能发货");
}
}
// 订单类
public class Order {
private OrderState state = new PendingState();
public void pay() {
state.pay(this); // 委托给状态对象
}
public void ship() {
state.ship(this);
}
}
与中介者模式对比
这两个模式容易混淆,但本质不同:
| 维度 | 中介者模式 | 状态模式 |
|---|---|---|
| 控制方式 | 中心化控制(中介者决定) | 去中心化控制(状态自己决定) |
| 对象化的是 | 交互逻辑 | 状态行为 |
| 关注点 | 对象间如何协作 | 当前状态该做什么 |
| 适用场景 | 多对象协作、交互复杂 | 状态转换复杂、路径多 |
举例说明:
- 中介者:订单流程需要协调库存、支付、通知等多个服务 → 用中介者统一编排
- 状态模式:订单本身有多个状态(待支付、已支付、已发货),每个状态行为不同 → 用状态模式
优缺点
优点:
- 消除了庞大的条件分支语句
- 符合开闭原则,新增状态无需修改现有代码
- 符合单一职责原则,每个状态类只负责一个状态的行为
缺点:
- 会增加类的数量
- 状态转换逻辑分散在各个状态类中,不够集中
适用场景
- 对象的行为取决于它的状态,且状态经常变化
- 代码中包含大量与状态相关的条件语句
策略模式(Strategy Pattern)
概念
策略模式是一种行为型设计模式,它定义了一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
通俗理解:去北京可以坐飞机、坐高铁、开车,这些都是"策略"。你可以根据时间、预算、舒适度选择不同的策略,但目标都是到达北京。
核心思想
把"做什么"(目标)和"怎么做"(算法)分开,让"怎么做"可以灵活变化。
关键点:
- 目标是固定的(到达北京)
- 实现方式可以变化(飞机、高铁、汽车)
- 客户端可以自由选择策略
核心角色
- Context(上下文):持有策略对象的引用,负责调用策略
- Strategy(策略接口):定义所有策略的公共接口
- ConcreteStrategy(具体策略):实现具体的算法逻辑
实际应用场景
场景:支付方式选择
// 策略接口
interface PaymentStrategy {
void pay(double amount);
}
// 具体策略
class AlipayStrategy implements PaymentStrategy {
public void pay(double amount) {
System.out.println("使用支付宝支付:" + amount);
}
}
class WechatStrategy implements PaymentStrategy {
public void pay(double amount) {
System.out.println("使用微信支付:" + amount);
}
}
// 上下文
class PaymentContext {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void pay(double amount) {
strategy.pay(amount);
}
}
// 使用
PaymentContext context = new PaymentContext();
context.setStrategy(new AlipayStrategy());
context.pay(100); // 使用支付宝
context.setStrategy(new WechatStrategy());
context.pay(200); // 切换到微信
优缺点
优点:
- 算法可以自由切换
- 避免使用多重条件判断
- 扩展性好,符合开闭原则
缺点:
- 策略类数量增多
- 客户端必须知道所有策略类
模板方法模式(Template Method Pattern)
概念
模板方法模式是一种行为型设计模式,它在父类中定义了一个算法的骨架(模板),允许子类在不改变算法整体结构的情况下,重写算法的某些特定步骤。
通俗理解:就像做菜的流程:备料 → 烹饪 → 装盘。这个流程是固定的(模板),但每道菜的具体做法不同(子类实现)。
核心思想
双重目的:
- 流程控制(最核心):定义算法骨架,保证流程不被破坏
- 代码复用(重要收益):提取公共代码,避免重复
模板模式的结构
AbstractClass (抽象类)
├── templateMethod() - 模板方法(final,定义算法骨架)
├── step1() - 抽象方法(子类必须实现)
├── step2() - 抽象方法(子类必须实现)
└── step3() - 钩子方法(可选实现,默认空实现)
ConcreteClass (具体类)
└── 实现抽象方法,提供具体步骤
代码示例
// 抽象类
abstract class DataProcessor {
// 模板方法(final防止被重写)
public final void process() {
readData(); // 步骤1
processData(); // 步骤2
writeData(); // 步骤3
if (needCleanup()) { // 钩子方法
cleanup();
}
}
protected abstract void readData(); // 子类必须实现
protected abstract void processData(); // 子类必须实现
protected abstract void writeData(); // 子类必须实现
// 钩子方法(可选实现)
protected boolean needCleanup() {
return false;
}
protected void cleanup() {
// 默认空实现
}
}
// 具体类
class CsvProcessor extends DataProcessor {
protected void readData() {
System.out.println("读取CSV文件");
}
protected void processData() {
System.out.println("处理CSV数据");
}
protected void writeData() {
System.out.println("写入CSV文件");
}
}
与策略模式对比
这两个模式经常被混淆,但目的完全不同:
| 维度 | 模板方法模式 | 策略模式 |
|---|---|---|
| 目的 | 保护流程、复用代码 | 算法可替换 |
| 流程 | 固定流程,不可变 | 没有固定流程 |
| 实现 | 继承(父类定义流程) | 组合(注入策略对象) |
| 重点 | 流程的稳定性 | 算法的灵活性 |
举例说明:支付场景
使用模板方法:
abstract class PaymentService {
public final void pay(Order order) {
validateOrder(order); // 固定流程
deductBalance(order); // 固定流程
createPaymentRecord(order); // 固定流程
sendNotification(order); // 固定流程
}
protected abstract void deductBalance(Order order); // 差异部分
}
class AlipayService extends PaymentService {
protected void deductBalance(Order order) {
// 支付宝扣款逻辑
}
}
优势:流程被保护,所有支付方式都必须经过完整流程,不会遗漏步骤。
使用策略模式:
interface PaymentStrategy {
void pay(Order order);
}
class AlipayStrategy implements PaymentStrategy {
public void pay(Order order) {
// 需要自己写完整流程
validateOrder(order);
deductBalance(order);
createPaymentRecord(order);
sendNotification(order);
// 容易遗漏步骤,且代码重复
}
}
核心价值
模板方法模式的核心价值在于流程保护:
- 确保关键步骤不被遗漏
- 确保步骤执行顺序正确
- 提取公共逻辑,避免重复
优缺点
优点:
- 封装不变部分,扩展可变部分
- 提取公共代码,便于维护
- 行为由父类控制,子类实现
缺点:
- 每个不同实现都需要一个子类,类数量增加
- 增加了系统复杂度
适用场景
- 多个类有相同的方法,且逻辑基本相同
- 重要、复杂的算法,可以把核心算法设计为模板方法
- 重构时,把相同代码抽取到父类
访问者模式(Visitor Pattern)
概念
访问者模式是一种行为型设计模式,它允许你在不修改已有类结构的前提下,定义作用于这些类元素的新操作。简单来说,就是把数据结构和数据操作分离开来。
通俗理解:博物馆的展品是固定的,但不同的参观者(艺术家、历史学家、商人)看同一个展品会关注不同的方面。展品不需要改变,只是访问者的视角不同。
核心思想
让操作可以独立于数据结构变化,通过双重分派实现二维多态。
关键理解:
- 一维多态:不同的对象调用同一个方法,表现不同(普通的多态)
- 二维多态:不同的对象 × 不同的操作 = 多种组合(访问者模式)
核心角色
- Visitor(访问者接口):声明访问各种元素的方法
- ConcreteVisitor(具体访问者):实现具体的访问操作
- Element(元素接口):定义accept方法,接受访问者
- ConcreteElement(具体元素):实现accept方法
- ObjectStructure(对象结构):存储元素集合,提供遍历接口
生动的案例:博物馆参观
场景设定:
展品(Element):稳定不变
- 青铜器
- 瓷器
- 书画
参观者(Visitor):各有所需
- 艺术家 → 看艺术价值
- 历史学家 → 看历史价值
- 商人 → 看商业价值
这就是二维多态:
艺术家 历史学家 商人
青铜器 ✓ ✓ ✓
瓷器 ✓ ✓ ✓
书画 ✓ ✓ ✓
同一个展品,不同的人看到不同的东西,产生9种不同的结果。
双重分派机制
第一次分派:根据Element类型(青铜器、瓷器、书画)
第二次分派:根据Visitor类型(艺术家、历史学家、商人)
// 元素接口
interface Artifact {
void accept(Visitor visitor);
}
// 具体元素
class BronzeWare implements Artifact {
public void accept(Visitor visitor) {
visitor.visit(this); // 第一次分派:确定是青铜器
}
}
// 访问者接口
interface Visitor {
void visit(BronzeWare bronze);
void visit(Porcelain porcelain);
}
// 具体访问者
class Artist implements Visitor {
public void visit(BronzeWare bronze) {
// 第二次分派:艺术家看青铜器
System.out.println("欣赏青铜器的造型美");
}
public void visit(Porcelain porcelain) {
System.out.println("欣赏瓷器的釉色美");
}
}
适用场景
适合使用访问者模式的情况:
- ✅ 数据结构稳定(展品不常变)
- ✅ 操作经常变化(参观者视角多样)
- ✅ 需要对一组对象进行很多不同的操作
不适合使用的情况:
- ❌ 数据结构经常变化(每次加新展品,所有访问者都要改)
- ❌ 操作比较固定
优缺点
优点:
- 符合单一职责原则,操作集中在访问者中
- 增加新操作很容易,只需增加新访问者
- 访问者可以累积状态
缺点:
- 增加新元素很困难,需要修改所有访问者
- 破坏了封装性,访问者需要访问元素的内部状态
实际应用
- 编译器的AST遍历(语法树节点稳定,操作多样)
- 报表系统(数据结构固定,输出格式多样)
- 对象序列化(对象结构稳定,序列化方式多样)
271



被折叠的 条评论
为什么被折叠?



