前言
从第一篇工厂模式开始,我会持续地更新每一种设计模式的内容,争取用通俗易懂的语言讲解和解释清楚。如果对你学习设计模式有帮助,请不要吝啬手中的赞~ 如果对文章内容有任何疑惑都可以在评论区提出和讨论~
本系列文章中的完整源码已上传 github 仓库,你可以在这里 github.com/FatMii/Desi...获取。
同样的,如果对你有帮助,请给我一个star~谢谢
设计模式合集链接:
Hello~ 大家好,本篇文章我们继续学习设计模式第八篇:状态模式
一、介绍
状态模式是一种行为型设计模式,其核心是将对象的状态与状态对应的行为分离,让对象在不同状态下自动切换对应的行为,而不是通过大量 if-else 或 switch-case 判断状态。简单来说,就是让 "状态自己决定该做什么"------ 比如红绿灯,红灯时车辆停止、行人等待,绿灯时车辆通行、行人停止,每个状态都有明确的行为,状态切换时行为也自动切换,无需外部频繁判断。
状态模式主要包含以下三个关键部分:
- 环境类(Context) :这是拥有状态的对象,它持有当前状态的引用,对外提供统一的接口(如 "切换状态"),并将与状态相关的行为委托给当前状态对象处理,自身不直接包含状态逻辑。
- 抽象状态类(State) :这是一个通用接口,定义了所有具体状态共有的行为方法(如 "处理当前状态逻辑""切换到下一个状态"),保证所有状态在行为上具有一致性。
- 具体状态类(Concrete State) :这些是实现了抽象状态接口的具体状态,每个类对应一个具体状态,包含该状态下的行为逻辑,以及状态切换的规则(比如红灯状态的下一个状态是绿灯)。
通过这种设计,状态模式能让代码摆脱复杂的条件判断,将每个状态的行为封装成独立类,不仅提高了代码的可读性和可维护性,还能轻松扩展新状态(如给红绿灯新增 "黄灯" 状态)。
二、前端场景:选项卡切换
在前端开发中,选项卡(Tab)是非常常见的组件 ------ 比如商品详情页的 "商品介绍""规格参数""用户评价" 切换,点击不同选项卡时,会显示对应内容并高亮当前选项。如果用传统 if-else 实现,会把所有状态判断和 DOM 操作混在一起,代码臃肿且难维护,而状态模式能让每个选项卡状态独立管理自己的行为。
1. 传统方式:混乱的条件判断
传统实现中,会通过判断当前激活的选项卡索引或 ID,来执行对应的 DOM 操作(显示内容、添加高亮样式),代码耦合度高:
js
// 传统选项卡:用if-else判断状态,逻辑耦合
class TabComponent {
constructor(tabContainer) {
// 获取DOM元素
this.tabs = tabContainer.querySelectorAll('.tab-item');
this.panels = tabContainer.querySelectorAll('.tab-panel');
// 当前激活的选项卡索引
this.activeIndex = 0;
// 初始化:激活第一个选项卡
this.init();
}
init() {
// 给每个选项卡绑定点击事件
this.tabs.forEach((tab, index) => {
tab.addEventListener('click', () => this.switchTab(index));
});
// 初始激活第一个
this.updateTabUI(this.activeIndex);
}
// 切换选项卡:核心逻辑(大量条件判断)
switchTab(targetIndex) {
// 避免重复点击同一选项卡
if (targetIndex === this.activeIndex) return;
// 1. 移除当前激活选项卡的高亮样式
this.tabs[this.activeIndex].classList.remove('active');
// 2. 隐藏当前激活的内容面板
this.panels[this.activeIndex].classList.add('hidden');
// 3. 更新当前激活索引
this.activeIndex = targetIndex;
// 4. 给目标选项卡添加高亮样式
this.tabs[targetIndex].classList.add('active');
// 5. 显示目标内容面板
this.panels[targetIndex].classList.remove('hidden');
// 如果后续需要给不同选项卡添加特殊逻辑(如"用户评价"加载评论数据)
// 会新增大量if-else:
// if (targetIndex === 2) {
// this.loadComments();
// } else if (targetIndex === 1) {
// this.loadSpecs();
// }
}
// 辅助方法:更新UI(初始调用)
updateTabUI(activeIndex) {
this.tabs[activeIndex].classList.add('active');
this.panels[activeIndex].classList.remove('hidden');
}
}
// 使用选项卡:传入容器DOM
const tabContainer = document.getElementById('product-tab');
new TabComponent(tabContainer);
这种方式的问题很明显:所有状态(选项卡索引)对应的行为(样式切换、内容显示、特殊逻辑)都写在 switchTab 方法中,新增选项卡或特殊逻辑时,必须修改该方法,违背 "开闭原则",代码会越来越臃肿。
2. 状态模式:状态与行为分离的优雅实现
用状态模式改造后,我们将每个选项卡视为一个 "状态",每个状态负责自己的行为(高亮样式、显示内容、特殊逻辑),环境类只负责管理状态切换,代码结构更清晰:
定义抽象状态类(State)
抽象状态接口定义每个选项卡状态必须实现的行为方法:
js
// 抽象状态类:选项卡状态接口
class TabState {
constructor(context) {
// 持有环境类引用(用于切换状态)
this.context = context;
}
// 行为1:激活当前状态(高亮选项卡、显示面板)
activate() {
throw new Error("激活状态方法未实现!");
}
// 行为2:切换到目标状态(由具体状态决定是否允许切换)
switchTo(targetState) {
throw new Error("切换状态方法未实现!");
}
}
实现具体状态类(Concrete State)
每个选项卡对应一个具体状态类,实现该状态下的行为逻辑(如激活时高亮样式、显示内容,以及切换规则):
js
// 具体状态1:商品介绍选项卡
class IntroTabState extends TabState {
constructor(context) {
super(context);
// 状态标识(对应选项卡索引)
this.index = 0;
}
// 激活当前状态:高亮选项卡 + 显示内容面板
activate() {
// 高亮当前选项卡
this.context.tabs[this.index].classList.add('active');
// 显示当前内容面板
this.context.panels[this.index].classList.remove('hidden');
console.log("激活【商品介绍】选项卡");
// 商品介绍的特殊逻辑(如初始化图片轮播)
this.initImageCarousel();
}
// 切换到目标状态:先取消当前状态激活,再让环境类切换到目标状态
switchTo(targetState) {
// 取消当前状态激活(移除高亮、隐藏面板)
this.context.tabs[this.index].classList.remove('active');
this.context.panels[this.index].classList.add('hidden');
// 通知环境类切换到目标状态
this.context.setCurrentState(targetState);
}
// 商品介绍的特殊逻辑(独立封装,不影响其他状态)
initImageCarousel() {
console.log("初始化商品图片轮播");
// 实际图片轮播初始化逻辑...
}
}
// 具体状态2:规格参数选项卡
class SpecTabState extends TabState {
constructor(context) {
super(context);
this.index = 1;
}
activate() {
this.context.tabs[this.index].classList.add('active');
this.context.panels[this.index].classList.remove('hidden');
console.log("激活【规格参数】选项卡");
// 规格参数的特殊逻辑(如加载规格数据)
this.loadSpecData();
}
switchTo(targetState) {
this.context.tabs[this.index].classList.remove('active');
this.context.panels[this.index].classList.add('hidden');
this.context.setCurrentState(targetState);
}
// 规格参数的特殊逻辑
loadSpecData() {
console.log("加载商品规格参数数据");
// 实际接口请求逻辑...
}
}
// 具体状态3:用户评价选项卡
class CommentTabState extends TabState {
constructor(context) {
super(context);
this.index = 2;
}
activate() {
this.context.tabs[this.index].classList.add('active');
this.context.panels[this.index].classList.remove('hidden');
console.log("激活【用户评价】选项卡");
// 用户评价的特殊逻辑(如加载评论列表)
this.loadCommentList();
}
switchTo(targetState) {
this.context.tabs[this.index].classList.remove('active');
this.context.panels[this.index].classList.add('hidden');
this.context.setCurrentState(targetState);
}
// 用户评价的特殊逻辑
loadCommentList() {
console.log("加载用户评价列表");
// 实际接口请求逻辑...
}
}
实现环境类(Context)
环境类管理所有状态,持有当前状态引用,对外提供切换状态的接口,并将行为委托给当前状态:
kotlin
// 环境类:选项卡组件(管理状态切换)
class TabContext {
constructor(tabContainer) {
// 1. 获取DOM元素
this.tabs = tabContainer.querySelectorAll('.tab-item');
this.panels = tabContainer.querySelectorAll('.tab-panel');
// 2. 初始化所有状态实例
this.introState = new IntroTabState(this); // 商品介绍状态
this.specState = new SpecTabState(this); // 规格参数状态
this.commentState = new CommentTabState(this); // 用户评价状态
// 3. 初始状态:商品介绍
this.currentState = this.introState;
// 4. 绑定事件(对外提供交互入口)
this.bindEvents();
// 5. 激活初始状态
this.currentState.activate();
}
// 绑定选项卡点击事件
bindEvents() {
// 点击"商品介绍"选项卡
this.tabs[0].addEventListener('click', () => {
if (this.currentState !== this.introState) {
this.currentState.switchTo(this.introState);
}
});
// 点击"规格参数"选项卡
this.tabs[1].addEventListener('click', () => {
if (this.currentState !== this.specState) {
this.currentState.switchTo(this.specState);
}
});
// 点击"用户评价"选项卡
this.tabs[2].addEventListener('click', () => {
if (this.currentState !== this.commentState) {
this.currentState.switchTo(this.commentState);
}
});
}
// 切换当前状态(由具体状态调用)
setCurrentState(newState) {
this.currentState = newState;
// 激活新状态的行为
this.currentState.activate();
}
}
使用状态模式
客户端只需传入选项卡容器 DOM,环境类会自动管理状态,使用方式简洁:
html
<!-- 选项卡HTML结构 -->
<div id="product-tab" class="tab-container">
<!-- 选项卡头部 -->
<div class="tab-header">
<div class="tab-item">商品介绍</div>
<div class="tab-item">规格参数</div>
<div class="tab-item">用户评价</div>
</div>
<!-- 选项卡内容面板 -->
<div class="tab-content">
<div class="tab-panel hidden">商品介绍内容:高清图片、商品特点...</div>
<div class="tab-panel hidden">规格参数内容:尺寸、材质、重量...</div>
<div class="tab-panel hidden">用户评价内容:好评率98%、用户留言...</div>
</div>
</div>
<script>
// 初始化选项卡(传入容器DOM)
const tabContainer = document.getElementById('product-tab');
new TabContext(tabContainer);
// 点击"规格参数"选项卡时的输出:
// 1. 取消当前状态(商品介绍):移除高亮、隐藏面板
// 2. 切换到规格参数状态
// 3. 激活规格参数状态:添加高亮、显示面板 → 输出"激活【规格参数】选项卡"
// 4. 执行特殊逻辑 → 输出"加载商品规格参数数据"
</script>
改造后的优势非常明显:如果需要新增 "售后服务" 选项卡,只需新增一个 ServiceTabState 类,在环境类中初始化并绑定事件,无需修改任何现有状态的代码;如果需要修改 "用户评价" 的加载逻辑,只需修改 CommentTabState 的 loadCommentList 方法,完全不影响其他状态,扩展性和可维护性大幅提升。
三、后端场景:订单状态流转
在后端开发中,订单状态流转是典型场景 ------ 订单有 "待支付"
"已支付"
"待发货"
"已发货"
"已完成"
等状态,每个状态下能执行的操作不同(如 "待支付" 状态可执行 "支付" 操作,"已支付" 状态可执行 "取消订单" 或 "申请发货" 操作)。用传统 if-else 实现会导致代码混乱,而状态模式能让每个状态管理自己的合法操作和状态切换规则。
1. 传统方式:冗余的状态判断
传统实现中,会通过 switch-case 判断订单当前状态,再决定是否允许执行操作(如支付、发货),代码冗余且易出错:
java
// 传统订单状态管理:用switch-case判断状态,逻辑耦合
public class OrderService {
// 订单状态枚举
public enum OrderStatus {
PENDING_PAYMENT, // 待支付
PAID, // 已支付
PENDING_SHIPMENT,// 待发货
SHIPPED, // 已发货
COMPLETED // 已完成
}
// 执行订单操作(支付、发货、确认收货等)
public String executeOperation(Order order, String operation) {
OrderStatus currentStatus = order.getStatus();
// 根据当前状态判断是否允许执行操作
switch (currentStatus) {
case PENDING_PAYMENT:
// 待支付状态:仅允许"支付"操作
if ("pay".equals(operation)) {
// 执行支付逻辑
order.setStatus(OrderStatus.PAID);
return "支付成功,订单状态更新为【已支付】";
} else if ("cancel".equals(operation)) {
// 待支付状态也允许"取消订单"
order.setStatus(OrderStatus.COMPLETED); // 取消后订单完成(简化)
return "订单取消成功,订单状态更新为【已完成】";
} else {
return "待支付状态不支持【" + operation + "】操作";
}
case PAID:
// 已支付状态:允许"申请发货""取消订单"
if ("ship".equals(operation)) {
order.setStatus(OrderStatus.PENDING_SHIPMENT);
return "申请发货成功,订单状态更新为【待发货】";
} else if ("cancel".equals(operation)) {
order.setStatus(OrderStatus.COMPLETED);
return "订单取消成功,订单状态更新为【已完成】";
} else {
return "已支付状态不支持【" + operation + "】操作";
}
case PENDING_SHIPMENT:
// 待发货状态:仅允许"发货"
if ("ship".equals(operation)) {
order.setStatus(OrderStatus.SHIPPED);
return "发货成功,订单状态更新为【已发货】";
} else {
return "待发货状态不支持【" + operation + "】操作";
}
case SHIPPED:
// 已发货状态:仅允许"确认收货"
if ("confirm".equals(operation)) {
order.setStatus(OrderStatus.COMPLETED);
return "确认收货成功,订单状态更新为【已完成】";
} else {
return "已发货状态不支持【" + operation + "】操作";
}
case COMPLETED:
// 已完成状态:不允许任何操作
return "已完成状态不支持任何操作";
default:
return "未知订单状态";
}
}
}
// 订单实体类
class Order {
private String orderId;
private OrderService.OrderStatus status;
// 构造器、getter、setter
public Order(String orderId, OrderService.OrderStatus status) {
this.orderId = orderId;
this.status = status;
}
public OrderService.OrderStatus getStatus() {
return status;
}
public void setStatus(OrderService.OrderStatus status) {
this.status = status;
}
}
// 使用传统订单服务
public class Main {
public static void main(String[] args) {
OrderService orderService = new OrderService();
// 创建待支付订单
Order order = new Order("ORDER001", OrderService.OrderStatus.PENDING_PAYMENT);
// 执行支付操作
System.out.println(orderService.executeOperation(order, "pay"));
// 输出:支付成功,订单状态更新为【已支付】
// 执行发货操作
System.out.println(orderService.executeOperation(order, "ship"));
// 输出:申请发货成功,订单状态更新为【待发货】
// 尝试在待发货状态执行"确认收货"(不允许)
System.out.println(orderService.executeOperation(order, "confirm"));
// 输出:待发货状态不支持【confirm】操作
}
}
这种方式的问题很突出:所有状态的操作规则都写在 executeOperation 方法中,新增状态(如 "退款中")或修改操作规则(如 "已支付" 状态不允许取消)时,必须修改该方法,代码会越来越复杂,且容易遗漏状态判断。
2. 状态模式改造
用状态模式改造后,我们将每个订单状态封装成独立类,每个状态管理自己的合法操作和状态切换规则,环境类(订单)只负责持有当前状态并委托行为:
定义抽象状态类(State)
抽象状态接口定义每个订单状态必须实现的行为方法(执行操作、获取状态标识):
java
// 抽象状态类:订单状态接口
public interface OrderState {
// 获取状态标识(如"待支付""已支付")
String getStateName();
// 执行操作(支付、发货、确认收货等),返回操作结果
String executeOperation(OrderContext order, String operation);
}
实现具体状态类(Concrete State)
每个订单状态对应一个具体状态类,实现该状态下的合法操作和状态切换逻辑:
java
// 具体状态1:待支付状态
public class PendingPaymentState implements OrderState {
@Override
public String getStateName() {
return "待支付";
}
@Override
public String executeOperation(OrderContext order, String operation) {
// 待支付状态仅支持"支付"和"取消订单"操作
switch (operation) {
case "pay":
// 执行支付逻辑(如扣减余额、生成支付记录)
System.out.println("执行支付逻辑:订单" + order.getOrderId());
// 切换到"已支付"状态
order.setState(new PaidState());
return "支付成功,订单状态更新为【" + order.getState().getStateName() + "】";
case "cancel":
// 执行取消订单逻辑(如释放库存)
System.out.println("执行取消订单逻辑:订单" + order.getOrderId());
// 切换到"已完成"状态(简化)
order.setState(new CompletedState());
return "订单取消成功,订单状态更新为【" + order.getState().getStateName() + "】";
default:
return "【" + getStateName() + "】状态不支持【" + operation + "】操作";
}
}
}
// 具体状态2:已支付状态
public class PaidState implements OrderState {
@Override
public String getStateName() {
return "已支付";
}
@Override
public String executeOperation(OrderContext order, String operation) {
// 已支付状态支持"申请发货"和"取消订单"
switch (operation) {
case "ship":
// 执行申请发货逻辑(如通知仓库)
System.out.println("执行申请发货逻辑:订单" + order.getOrderId());
// 切换到"待发货"状态
order.setState(new PendingShipmentState());
return "申请发货成功,订单状态更新为【" + order.getState().getStateName() + "】";
case "cancel":
// 执行取消订单逻辑(如退款、释放库存)
System.out.println("执行取消订单逻辑(含退款):订单" + order.getOrderId());
// 切换到"已完成"状态
order.setState(new CompletedState());
return "订单取消成功(已退款),订单状态更新为【" + order.getState().getStateName() + "】";
default:
return "【" + getStateName() + "】状态不支持【" + operation + "】操作";
}
}
}
// 具体状态3:待发货状态
public class PendingShipmentState implements OrderState {
@Override
public String getStateName() {
return "待发货";
}
@Override
public String executeOperation(OrderContext order, String operation) {
// 待发货状态仅支持"发货"操作
if ("ship".equals(operation)) {
// 执行发货逻辑(如生成物流单)
System.out.println("执行发货逻辑:订单" + order.getOrderId());
// 切换到"已发货"状态
order.setState(new ShippedState());
return "发货成功,订单状态更新为【" + order.getState().getStateName() + "】";
} else {
return "【" + getStateName() + "】状态不支持【" + operation + "】操作";
}
}
}
// 具体状态4:已发货状态
public class ShippedState implements OrderState {
@Override
public String getStateName() {
return "已发货";
}
@Override
public String executeOperation(OrderContext order, String operation) {
// 已发货状态仅支持"确认收货"操作
if ("confirm".equals(operation)) {
// 执行确认收货逻辑(如结算商家款项)
System.out.println("执行确认收货逻辑:订单" + order.getOrderId());
// 切换到"已完成"状态
order.setState(new CompletedState());
return "确认收货成功,订单状态更新为【" + order.getState().getStateName() + "】";
} else {
return "【" + getStateName() + "】状态不支持【" + operation + "】操作";
}
}
}
// 具体状态5:已完成状态
public class CompletedState implements OrderState {
@Override
public String getStateName() {
return "已完成";
}
@Override
public String executeOperation(OrderContext order, String operation) {
// 已完成状态不支持任何操作
return "【" + getStateName() + "】状态不支持任何操作";
}
}
实现环境类(Context)
环境类(订单)持有当前状态引用,对外提供执行操作和切换状态的接口,自身不包含状态逻辑:
java
// 环境类:订单(管理状态和委托行为)
public class OrderContext {
private String orderId;
private OrderState state; // 当前状态
// 构造器:初始化订单ID和初始状态(默认待支付)
public OrderContext(String orderId) {
this.orderId = orderId;
this.state = new PendingPaymentState(); // 初始状态为待支付
}
// 执行订单操作(对外统一接口)
public String executeOperation(String operation) {
// 将操作委托给当前状态处理
return state.executeOperation(this, operation);
}
// 切换状态(由具体状态类调用)
public void setState(OrderState newState) {
this.state = newState;
}
// 获取当前状态名称(对外展示用)
public String getCurrentStateName() {
return state.getStateName();
}
// getter:供具体状态类获取订单ID
public String getOrderId() {
return orderId;
}
}
使用状态模式
客户端只需创建订单环境类,调用 executeOperation
方法即可,无需关心状态判断和切换逻辑:
java
// 使用状态模式的订单服务
public class StatePatternMain {
public static void main(String[] args) {
// 1. 创建订单(初始状态为"待支付")
OrderContext order = new OrderContext("ORDER001");
System.out.println("初始订单状态:" + order.getCurrentStateName());
// 输出:初始订单状态:待支付
// 2. 执行"支付"操作
System.out.println(order.executeOperation("pay"));
// 输出:执行支付逻辑:订单ORDER001 → 支付成功,订单状态更新为【已支付】
// 3. 执行"申请发货"操作
System.out.println(order.executeOperation("ship"));
// 输出:执行申请发货逻辑:订单ORDER001 → 申请发货成功,订单状态更新为【待发货】
// 4. 执行"发货"操作
System.out.println(order.executeOperation("ship"));
// 输出:执行发货逻辑:订单ORDER001 → 发货成功,订单状态更新为【已发货】
// 5. 执行"确认收货"操作
System.out.println(order.executeOperation("confirm"));
// 输出:执行确认收货逻辑:订单ORDER001 → 确认收货成功,订单状态更新为【已完成】
// 6. 尝试在已完成状态执行"取消"操作(不允许)
System.out.println(order.executeOperation("cancel"));
// 输出:【已完成】状态不支持任何操作
}
}
实际业务扩展:新增 "退款中" 状态
如果业务需要新增 "退款中" 状态(如用户在 "已发货" 状态申请退款),只需新增一个具体状态类,无需修改任何现有代码:
java
// 新增具体状态:退款中状态
public class RefundingState implements OrderState {
@Override
public String getStateName() {
return "退款中";
}
@Override
public String executeOperation(OrderContext order, String operation) {
// 退款中状态仅支持"退款完成"操作
if ("refund_complete".equals(operation)) {
System.out.println("执行退款完成逻辑:订单" + order.getOrderId());
order.setState(new CompletedState());
return "退款完成,订单状态更新为【" + order.getState().getStateName() + "】";
} else {
return "【" + getStateName() + "】状态不支持【" + operation + "】操作";
}
}
}
// 同时修改"已发货"状态,允许执行"申请退款"操作
public class ShippedState implements OrderState {
@Override
public String getStateName() {
return "已发货";
}
@Override
public String executeOperation(OrderContext order, String operation) {
if ("confirm".equals(operation)) {
// 原确认收货逻辑...
} else if ("apply_refund".equals(operation)) { // 新增"申请退款"操作
System.out.println("执行申请退款逻辑:订单" + order.getOrderId());
order.setState(new RefundingState()); // 切换到退款中状态
return "申请退款成功,订单状态更新为【" + order.getState().getStateName() + "】";
} else {
return "【" + getStateName() + "】状态不支持【" + operation + "】操作";
}
}
}
这种扩展方式完全符合 "开闭原则",新增状态时无需修改现有代码,极大降低了维护成本。
总结:状态模式的核心价值
状态模式的核心价值在于 "状态自治":让每个状态自己管理对应的行为和切换规则,替代复杂的条件判断,实现 "状态决定行为" 的逻辑。它解决了传统 if-else 代码臃肿、难维护的问题,同时让状态扩展更灵活,符合 "开闭原则"。
下次当你面对满屏的 if-else 状态判断时,不妨试试状态模式,让每个状态 "自己做主",让代码变得更优雅、更易维护!