状态模式(State Pattern)
状态模式是行为型设计模式的核心成员,其核心思想是将对象的状态封装为独立类,让对象在不同状态下表现出不同行为(仿佛 “切换了自身类型”),同时将状态切换逻辑委托给具体状态类,彻底消除代码中冗长的 if-else 或 switch-case 判断。
定义
官方定义(参考《设计模式:可复用面向对象软件的基础》): Allow an object to alter its behavior when its internal state changes. The object will appear to change its class. (当对象的内部状态发生改变时,允许它改变自己的行为,这个对象看起来就像是改变了它的类一样。)
通俗理解: 以 “手机” 为例,它有 “开机”“关机”“待机”“通话” 4 种状态 ——
- 关机状态:仅支持 “开机” 操作,不支持 “通话”“待机”;
- 开机状态:支持 “待机”“通话” 操作,不支持 “重复开机”;
- 通话状态:支持 “挂断” 操作,不支持 “关机”。 状态模式会将每种状态的 “可执行操作” 和 “状态切换规则” 封装成独立类,手机对象只需委托当前状态类处理操作,无需自己判断 “我现在能做什么”。
意图
状态模式的核心目标是解决以下问题:
- 消除状态相关的条件判断:避免对象因状态过多而出现
if-else嵌套(如if (state == 待支付) { ... } else if (state == 已支付) { ... }),降低代码复杂度; - 封装状态与行为:将 “状态” 和 “该状态下的行为” 强绑定,符合单一职责原则(每个状态类只负责自身逻辑);
- 简化状态切换:状态切换逻辑由具体状态类控制(而非分散在环境类中),新增 / 修改状态时无需改动原有代码,符合开闭原则。
结构
状态模式包含 3 个核心角色,复杂场景会扩展 “状态管理器”,各角色职责清晰:
| 角色名称 | 核心职责 |
|---|---|
| 环境类(Context) | 1. 持有当前状态的引用;2. 对外暴露业务接口(如 “开机”“通话”),并委托给当前状态处理;3. 提供 setState() 方法,允许状态类修改自身状态。 |
| 抽象状态(State) | 定义所有具体状态的统一接口,声明环境类需委托的行为(如 handlePowerOn()、handleCall())。 |
| 具体状态(ConcreteState) | 1. 实现抽象状态的接口,定义该状态下的具体行为(如 “关机状态下开机”);2. 负责状态切换(如 “关机→开机”)。 |
| (可选)状态管理器 | 统一管理所有状态实例(如单例池),避免重复创建状态对象,简化状态获取逻辑。 |
类图(Mermaid 版)

以 “手机状态管理” 为场景(关机→开机→待机→通话),用 Mermaid 类图展示状态模式结构:
classDiagram
direction TB
%% 1. 环境类(Context):手机对象,持有当前状态
class Phone {
- phoneId: String
- currentState: PhoneState // 核心:持有当前状态引用
- battery: int // 电量(辅助属性)
+ Phone(phoneId: String)
+ powerOn(): void // 对外接口:开机
+ powerOff(): void // 对外接口:关机
+ makeCall(): void // 对外接口:打电话
+ endCall(): void // 对外接口:挂断电话
+ setCurrentState(state: PhoneState): void // 允许状态切换
+ getPhoneInfo(): String // 辅助:获取手机信息
}
%% 2. 抽象状态(State):定义手机行为接口
class PhoneState {
<<interface>>
+ handlePowerOn(phone: Phone): void // 处理开机
+ handlePowerOff(phone: Phone): void // 处理关机
+ handleMakeCall(phone: Phone): void // 处理打电话
+ handleEndCall(phone: Phone): void // 处理挂断
+ getStateName(): String // 辅助:获取状态名称
}
%% 3. 具体状态(ConcreteState):实现各状态逻辑
class PoweredOffState {
- static INSTANCE: PoweredOffState // 单例:避免重复创建
+ static getInstance(): PoweredOffState
+ handlePowerOn(phone: Phone): void // 关机→开机
+ handlePowerOff(phone: Phone): void // 关机不支持重复关机
+ handleMakeCall(phone: Phone): void // 关机不支持打电话
+ handleEndCall(phone: Phone): void // 关机不支持挂断
+ getStateName(): String
}
class PoweredOnState {
- static INSTANCE: PoweredOnState
+ static getInstance(): PoweredOnState
+ handlePowerOn(phone: Phone): void // 开机不支持重复开机
+ handlePowerOff(phone: Phone): void // 开机→关机
+ handleMakeCall(phone: Phone): void // 开机→通话
+ handleEndCall(phone: Phone): void // 开机不支持挂断(未通话)
+ getStateName(): String
}
class CallingState {
- static INSTANCE: CallingState
+ static getInstance(): CallingState
+ handlePowerOn(phone: Phone): void // 通话中不支持开机
+ handlePowerOff(phone: Phone): void // 通话中不支持关机
+ handleMakeCall(phone: Phone): void // 通话中不支持重复打电话
+ handleEndCall(phone: Phone): void // 通话→开机(待机)
+ getStateName(): String
}
%% 角色关系
Phone "1" --> "1" PhoneState : 持有当前状态 >
PoweredOffState ..|> PhoneState
PoweredOnState ..|> PhoneState
CallingState ..|> PhoneState
时序图(Mermaid 版)
以 “用户开机→打电话→挂断” 为场景,展示状态模式的交互流程,体现委托逻辑和状态切换:
sequenceDiagram
participant Client as 客户端(用户)
participant Phone as 环境类(手机)
participant OffState as 具体状态(关机)
participant OnState as 具体状态(开机)
participant CallState as 具体状态(通话)
%% 1. 初始化:创建手机,默认状态为“关机”
Client->>Phone: 1. new Phone("P12345")
Note over Phone: 初始化 currentState = OffState.getInstance()
Phone-->>Client: 手机创建成功,当前状态:关机
%% 2. 用户触发开机操作
Client->>Phone: 2. powerOn() // 调用手机开机接口
Note over Phone: 委托当前状态(关机)处理
%% 3. 手机委托关机状态处理开机
Phone->>OffState: 3. handlePowerOn(this)
Note over OffState: 1. 校验状态(关机允许开机)<br>2. 模拟开机逻辑(加载系统)
%% 4. 关机状态切换手机状态为“开机”
OffState->>Phone: 4. setCurrentState(OnState.getInstance())
Note over Phone: currentState 变为 开机状态
%% 5. 开机结果返回客户端
OffState-->>Phone: 开机成功
Phone-->>Client: 手机已开机,当前状态:开机
%% 6. 用户触发打电话操作
Client->>Phone: 6. makeCall()
Note over Phone: 委托当前状态(开机)处理
%% 7. 手机委托开机状态处理打电话
Phone->>OnState: 7. handleMakeCall(this)
Note over OnState: 1. 校验状态(开机允许打电话)<br>2. 模拟拨号逻辑
%% 8. 开机状态切换手机状态为“通话”
OnState->>Phone: 8. setCurrentState(CallState.getInstance())
Note over Phone: currentState 变为 通话状态
%% 9. 打电话结果返回客户端
OnState-->>Phone: 通话已接通
Phone-->>Client: 正在通话中,当前状态:通话
%% 10. 用户触发挂断操作
Client->>Phone: 10. endCall()
Note over Phone: 委托当前状态(通话)处理
%% 11. 手机委托通话状态处理挂断
Phone->>CallState: 11. handleEndCall(this)
Note over CallState: 1. 校验状态(通话允许挂断)<br>2. 模拟挂断逻辑
%% 12. 通话状态切换手机状态为“开机”
CallState->>Phone: 12. setCurrentState(OnState.getInstance())
Note over Phone: currentState 变为 开机状态
%% 13. 挂断结果返回客户端
CallState-->>Phone: 通话已挂断
Phone-->>Client: 通话结束,当前状态:开机
模式分析
1. 核心逻辑:委托与双分派
状态模式的行为触发依赖双分派机制,确保 “不同状态下,同一方法表现不同行为”:
- 第一次分派:客户端调用环境类接口(如
Phone.makeCall()),由环境类确定 “当前状态对象”; - 第二次分派:环境类调用当前状态的对应方法(如
currentState.handleMakeCall(this)),由状态对象的实际类型确定具体逻辑。
例如:Phone.makeCall() → 若当前是 OnState,则执行 “开机→通话”;若当前是 CallState,则提示 “通话中不支持重复拨号”。
2. 与策略模式的关键区别
状态模式常与策略模式混淆(两者结构相似),但意图和逻辑完全不同:
| 对比维度 | 状态模式(State) | 策略模式(Strategy) |
|---|---|---|
| 核心意图 | 处理对象的 “状态变化”,行为随状态自动切换 | 封装 “不同算法 / 策略”,由客户端主动选择 |
| 状态 / 策略关系 | 状态间存在依赖(如关机→开机→通话) | 策略间相互独立(如微信支付 / 支付宝支付) |
| 切换主体 | 状态切换由具体状态类控制(内部决定) | 策略切换由客户端控制(外部决定) |
| 行为约束 | 状态对行为有约束(如关机不能打电话) | 策略无约束(选任一策略均可完成同一目标) |
优点
- 消除冗余条件判断:用 “状态类 + 委托” 替代
if-else,代码更简洁、可读性更高,减少 “复制粘贴” 错误; - 符合设计原则:
- 单一职责:每个状态类仅负责自身的行为和切换逻辑;
- 开闭原则:新增状态只需添加
ConcreteState类,无需修改环境类或其他状态类;
- 状态逻辑集中:状态的行为和切换规则集中在状态类中,便于调试和维护(如修改 “开机逻辑” 只需改
PoweredOffState); - 行为一致性:所有状态实现同一接口,确保环境类的接口调用稳定(客户端无需关心状态细节)。
缺点
- 类数量膨胀:每个状态对应一个类,若状态过多(如 10 + 种),会导致类数量激增(如电商订单有 8 种状态,需 8 个状态类);
- 状态耦合增强:具体状态类需知道其他状态类(如
PoweredOffState需调用OnState.getInstance()),增加了状态间的依赖; - 学习成本较高:相比直接用
if-else,状态模式的结构更复杂,团队需理解 “委托逻辑” 和 “双分派” 才能正确使用; - 简单场景过度设计:若状态少(仅 2-3 种)且逻辑简单,用状态模式会增加代码复杂度(不如
if-else直观)。
适用环境
当对象满足以下条件时,适合使用状态模式:
- 对象的行为依赖于其内部状态,且状态变化时行为需同步改变(如手机、订单、播放器);
- 代码中存在大量与状态相关的条件判断,且判断逻辑重复或嵌套层级深(如
if (stateA) { ... } else if (stateB) { ... } else if (stateC) { ... }); - 状态数量明确,且未来可能扩展新状态(如电商订单可能新增 “已退款”“已售后” 状态);
- 状态切换规则清晰,且可封装在具体状态类中(如 “关机→开机”“开机→通话” 的规则无歧义)。
模式应用
状态模式在实际开发和框架中应用广泛,典型场景包括:
1. 业务场景
- 电商领域:订单状态(待支付→已支付→已发货→已签收→已取消)、物流状态(待揽收→运输中→派送中→已签收);
- 多媒体领域:视频播放器(Idle→Prepared→Playing→Paused→Stopped)、音频 recorder(Idle→Recording→Stopped);
- 设备控制:电梯(开门→关门→运行→停止)、红绿灯(红灯→绿灯→黄灯)、智能门锁(未上锁→已上锁→临时开锁)。
2. 框架源码
- Android MediaPlayer:核心组件,生命周期包含 10 种状态(Idle、Initialized、Prepared 等),所有操作(
start()/pause())均需校验当前状态,否则抛异常; - Spring StateMachine:基于状态模式的状态机框架,支持复杂状态流转(如订单流程、工作流),提供状态持久化、事件驱动等能力;
- Java Thread.State:线程的 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED),但
Thread未完全遵循状态模式(行为未委托给状态类,仅用枚举标识状态)。
模式扩展
1. 状态管理器(State Manager)
当状态数量较多时,新增 “状态管理器” 统一管理所有状态实例,避免每个 ConcreteState 重复写单例逻辑,同时简化状态获取:
// 手机状态管理器:统一管理所有状态实例
public class PhoneStateManager {
// 状态池:存储状态标识与实例的映射
private static final Map<String, PhoneState> STATE_MAP = new HashMap<>();
// 静态初始化:加载所有状态
static {
STATE_MAP.put("POWERED_OFF", PoweredOffState.getInstance());
STATE_MAP.put("POWERED_ON", PoweredOnState.getInstance());
STATE_MAP.put("CALLING", CallingState.getInstance());
}
// 根据状态标识获取状态实例(如从数据库加载时使用)
public static PhoneState getState(String stateCode) {
return STATE_MAP.getOrDefault(stateCode, PoweredOffState.getInstance());
}
}
2. 状态持久化
在分布式系统中,需将状态持久化到数据库(如手机状态存 phone_state 字段),恢复时通过状态管理器加载对应状态:
// 从数据库加载手机信息时,恢复状态
public Phone loadPhoneFromDB(String phoneId) {
// 1. 从数据库查询手机信息(phoneId、battery、stateCode)
String dbStateCode = "POWERED_ON"; // 假设查询结果为“开机”
int dbBattery = 80; // 假设电量80%
// 2. 通过状态管理器获取对应状态实例
PhoneState state = PhoneStateManager.getState(dbStateCode);
// 3. 构建手机对象并设置状态
Phone phone = new Phone(phoneId);
phone.setCurrentState(state);
phone.setBattery(dbBattery); // 假设Phone有setBattery方法
return phone;
}
3. 分层状态(Hierarchical State)
当状态存在 “父子关系” 时,可设计分层状态(如 “开机状态” 包含 “待机”“通话”“充电” 子状态),父状态实现公共行为,子状态实现差异化行为:
// 父状态:开机状态(包含子状态)
public class PoweredOnState implements PhoneState {
// 子状态:待机、通话、充电
private PhoneState subState;
@Override
public void handlePowerOff(phone: Phone) {
// 父状态公共行为:所有开机子状态均可关机
phone.setCurrentState(PoweredOffState.getInstance());
}
// 子状态差异化行为:委托给子状态处理
@Override
public void handleMakeCall(phone: Phone) {
subState.handleMakeCall(phone);
}
}
Android 中的应用
Android 框架和开发中,状态模式的思想和实践非常普遍:
1. MediaPlayer 状态管理
MediaPlayer 是 Android 多媒体核心组件,严格遵循状态模式的设计思想:
- 环境类:
MediaPlayer本身,持有当前状态(如mState变量); - 抽象状态:无显式接口,但内部通过
mState变量标识状态(如STATE_IDLE、STATE_PREPARED); - 具体状态:内部通过
checkState()方法校验状态,不同状态下执行不同行为(如start()仅在STATE_PREPARED/STATE_PAUSED状态下有效); - 行为约束:若在错误状态调用方法(如
STATE_IDLE调用start()),会抛出IllegalStateException,确保状态合法性。
2. View 状态与 StateListDrawable
Android View 的状态(如 Pressed、Focused、Selected)通过 StateListDrawable 管理,本质是状态模式的简化:
- 环境类:
View,持有当前状态(如mDrawableState数组); - 抽象状态:
StateListDrawable定义状态集合(如android.R.attr.state_pressed); - 具体状态:每个状态对应一个
Drawable(如按下状态对应pressed_bg,正常状态对应normal_bg); - 行为:View 状态变化时(如用户触摸),
StateListDrawable自动加载对应状态的Drawable,无需开发者手动切换。
3. Activity 生命周期(类状态模式)
Activity 的生命周期虽由系统回调触发,但思想与状态模式一致:
- 状态:
Created→Started→Resumed→Paused→Stopped→Destroyed; - 行为约束:不同状态下执行不同操作(如
onResume()时界面可见可交互,onPause()时需暂停耗时操作); - 状态切换:由系统控制(如用户按 Home 键触发
onPause()→onStop()),开发者只需在对应回调中实现逻辑,无需手动判断状态。
4. 自定义 View 状态管理
开发自定义 View 时(如自定义按钮、进度条),常需用状态模式管理自定义状态:
// 自定义按钮:支持“加载中”状态
public class LoadingButton extends Button {
// 自定义状态:加载中
private boolean isLoading;
@Override
protected int[] onCreateDrawableState(int extraSpace) {
// 1. 扩展状态数组,添加“加载中”状态
int[] state = super.onCreateDrawableState(extraSpace + 1);
if (isLoading) {
mergeDrawableStates(state, new int[]{R.attr.state_loading});
}
return state;
}
// 切换“加载中”状态
public void setLoading(boolean loading) {
isLoading = loading;
// 2. 状态变化时,刷新View
refreshDrawableState();
}
}
// 布局中使用:通过StateListDrawable定义状态对应的背景
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/loading_bg" android:state_loading="true"/>
<item android:drawable="@drawable/normal_bg" android:state_loading="false"/>
</selector>
代码实现 手机状态管理
基于 “手机状态管理” 场景,完整实现状态模式:
1. 步骤 1:定义抽象状态接口(PhoneState)
// 抽象状态:定义手机行为的统一接口
public interface PhoneState {
// 处理开机
void handlePowerOn(Phone phone);
// 处理关机
void handlePowerOff(Phone phone);
// 处理打电话
void handleMakeCall(Phone phone);
// 处理挂断电话
void handleEndCall(Phone phone);
// 获取状态名称(辅助打印)
String getStateName();
}
2. 步骤 2:实现具体状态类
关机状态(PoweredOffState)
// 具体状态1:关机状态
public class PoweredOffState implements PhoneState {
// 单例:避免重复创建状态对象(状态无内部变量,可共享)
private static final PoweredOffState INSTANCE = new PoweredOffState();
// 私有构造:防止外部实例化
private PoweredOffState() {}
// 全局获取单例
public static PoweredOffState getInstance() {
return INSTANCE;
}
@Override
public void handlePowerOn(Phone phone) {
System.out.println("=== 处理开机操作 ===");
// 1. 模拟开机逻辑(如加载系统、初始化硬件)
System.out.println("手机正在开机...");
// 2. 切换状态为“开机”
phone.setCurrentState(PoweredOnState.getInstance());
System.out.println("开机成功!当前状态:" + phone.getCurrentState().getStateName());
}
@Override
public void handlePowerOff(Phone phone) {
// 关机状态不支持重复关机
System.err.println("错误:手机当前已关机,无需重复关机!");
}
@Override
public void handleMakeCall(Phone phone) {
// 关机状态不支持打电话
System.err.println("错误:手机当前已关机,无法打电话!");
}
@Override
public void handleEndCall(Phone phone) {
// 关机状态不支持挂断
System.err.println("错误:手机当前已关机,无通话可挂断!");
}
@Override
public String getStateName() {
return "关机";
}
}
开机状态(PoweredOnState)
// 具体状态2:开机状态(待机)
public class PoweredOnState implements PhoneState {
private static final PoweredOnState INSTANCE = new PoweredOnState();
private PoweredOnState() {}
public static PoweredOnState getInstance() {
return INSTANCE;
}
@Override
public void handlePowerOn(Phone phone) {
System.err.println("错误:手机当前已开机,无需重复开机!");
}
@Override
public void handlePowerOff(Phone phone) {
System.out.println("=== 处理关机操作 ===");
System.out.println("手机正在关机...");
phone.setCurrentState(PoweredOffState.getInstance());
System.out.println("关机成功!当前状态:" + phone.getCurrentState().getStateName());
}
@Override
public void handleMakeCall(Phone phone) {
System.out.println("=== 处理打电话操作 ===");
System.out.println("正在拨号...");
// 切换状态为“通话”
phone.setCurrentState(CallingState.getInstance());
System.out.println("通话已接通!当前状态:" + phone.getCurrentState().getStateName());
}
@Override
public void handleEndCall(Phone phone) {
System.err.println("错误:手机当前无通话,无需挂断!");
}
@Override
public String getStateName() {
return "开机(待机)";
}
}
通话状态(CallingState)
// 具体状态3:通话状态
public class CallingState implements PhoneState {
private static final CallingState INSTANCE = new CallingState();
private CallingState() {}
public static CallingState getInstance() {
return INSTANCE;
}
@Override
public void handlePowerOn(Phone phone) {
System.err.println("错误:手机正在通话中,无法开机!");
}
@Override
public void handlePowerOff(Phone phone) {
System.err.println("错误:手机正在通话中,无法关机!");
}
@Override
public void handleMakeCall(Phone phone) {
System.err.println("错误:手机正在通话中,无法重复打电话!");
}
@Override
public void handleEndCall(Phone phone) {
System.out.println("=== 处理挂断操作 ===");
System.out.println("正在挂断电话...");
// 切换状态为“开机(待机)”
phone.setCurrentState(PoweredOnState.getInstance());
System.out.println("通话已挂断!当前状态:" + phone.getCurrentState().getStateName());
}
@Override
public String getStateName() {
return "通话中";
}
}
3. 步骤 3:实现环境类(Phone)
// 环境类:手机,持有当前状态,委托行为处理
public class Phone {
private final String phoneId;
private PhoneState currentState; // 核心:持有当前状态引用
private int battery = 100; // 默认电量100%
// 初始化:默认状态为“关机”
public Phone(String phoneId) {
this.phoneId = phoneId;
this.currentState = PoweredOffState.getInstance();
System.out.println("手机创建成功!" + getPhoneInfo() + ",当前状态:" + currentState.getStateName());
}
// 对外接口:开机(委托给当前状态)
public void powerOn() {
currentState.handlePowerOn(this);
// 模拟开机耗电
battery -= 10;
}
// 对外接口:关机(委托给当前状态)
public void powerOff() {
currentState.handlePowerOff(this);
}
// 对外接口:打电话(委托给当前状态)
public void makeCall() {
currentState.handleMakeCall(this);
// 模拟通话耗电
battery -= 5;
}
// 对外接口:挂断电话(委托给当前状态)
public void endCall() {
currentState.handleEndCall(this);
}
// 允许状态类修改当前状态(包访问权限,避免外部随意修改)
void setCurrentState(PhoneState currentState) {
this.currentState = currentState;
}
// 获取当前状态(辅助)
public PhoneState getCurrentState() {
return currentState;
}
// 获取手机信息(辅助打印)
public String getPhoneInfo() {
return "手机ID:" + phoneId + ",电量:" + battery + "%";
}
// 设置电量(辅助,如充电逻辑)
public void setBattery(int battery) {
this.battery = Math.max(0, Math.min(100, battery));
}
}
4. 步骤 4:客户端测试
// 客户端:模拟用户操作手机
public class StatePatternTest {
public static void main(String[] args) {
// 1. 创建手机(初始状态:关机)
Phone phone = new Phone("P123456");
System.out.println("------------------------");
// 2. 测试1:关机状态下开机
phone.powerOn();
System.out.println("当前手机信息:" + phone.getPhoneInfo());
System.out.println("------------------------");
// 3. 测试2:开机状态下打电话
phone.makeCall();
System.out.println("当前手机信息:" + phone.getPhoneInfo());
System.out.println("------------------------");
// 4. 测试3:通话状态下重复打电话(预期失败)
phone.makeCall();
System.out.println("------------------------");
// 5. 测试4:通话状态下挂断
phone.endCall();
System.out.println("当前手机信息:" + phone.getPhoneInfo());
System.out.println("------------------------");
// 6. 测试5:开机状态下关机
phone.powerOff();
System.out.println("------------------------");
// 7. 测试6:关机状态下打电话(预期失败)
phone.makeCall();
}
}
5. 运行结果
手机创建成功!手机ID:P123456,电量:100%,当前状态:关机
------------------------
=== 处理开机操作 ===
手机正在开机...
开机成功!当前状态:开机(待机)
当前手机信息:手机ID:P123456,电量:90%
------------------------
=== 处理打电话操作 ===
正在拨号...
通话已接通!当前状态:通话中
当前手机信息:手机ID:P123456,电量:85%
------------------------
错误:手机正在通话中,无法重复打电话!
------------------------
=== 处理挂断操作 ===
正在挂断电话...
通话已挂断!当前状态:开机(待机)
当前手机信息:手机ID:P123456,电量:85%
------------------------
=== 处理关机操作 ===
手机正在关机...
关机成功!当前状态:关机
------------------------
错误:手机当前已关机,无法打电话!
代码实现 Android 客户端订单状态
以下是一个基于 Android 平台的订单管理实例,结合状态模式与 Android 架构组件(ViewModel、LiveData),实现订单状态的灵活管理与 UI 联动。实例模拟电商 App 中的订单流程,包含状态切换、UI 实时更新、网络请求模拟等核心功能。
功能场景
- 订单状态:待支付 → 已支付 → 已发货 → 已签收 → 已取消(支持退款场景)
- 用户操作:支付、取消、查看物流、确认收货、申请退款
- 自动行为:超时未支付自动取消(模拟)
- UI 响应:状态变化时自动更新按钮状态、显示对应操作提示
实现架构
├── data
│ ├── model
│ │ ├── Order.java // 订单数据模型(环境类)
│ │ └── OrderState.java // 状态接口
│ └── state
│ ├── PendingPayState.java // 待支付状态
│ ├── PaidState.java // 已支付状态
│ ├── ShippedState.java // 已发货状态
│ ├── ReceivedState.java // 已签收状态
│ └── CancelledState.java // 已取消状态
├── util
│ └── OrderStateManager.java // 状态管理器
└── ui
├── OrderViewModel.java // 管理订单数据与状态
└── OrderActivity.java // 订单详情页(UI展示)
完整代码实现
1. 订单模型与状态接口
OrderState.java(抽象状态接口)
package com.example.orderdemo.data.model;
import android.content.Context;
/**
* 抽象状态接口:定义所有订单状态的操作
*/
public interface OrderState {
// 处理支付
void handlePay(Order order, Context context);
// 处理取消订单
void handleCancel(Order order, Context context);
// 处理发货(商家操作,客户端仅显示)
void handleShip(Order order, String logisticsNo);
// 处理确认收货
void handleReceive(Order order, Context context);
// 处理退款
void handleRefund(Order order, Context context);
// 获取状态名称(用于UI显示)
String getStateName();
// 获取状态编码(用于持久化)
String getStateCode();
// 获取当前状态下可用的操作按钮
int[] getAvailableActions();
}
Order.java(订单模型 - 环境类)
package com.example.orderdemo.data.model;
import android.content.Context;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import com.example.orderdemo.util.OrderStateManager;
import java.util.Date;
import java.util.UUID;
/**
* 订单实体类(环境类)
*/
public class Order {
private static final String TAG = "Order";
private final String orderId;
private final String productName;
private final double price;
private final Date createTime;
private String logisticsNo;
private Date payTime;
private Date shipTime;
private Date receiveTime;
private Date cancelTime;
private Date refundTime;
// 核心:当前状态(使用LiveData便于UI观察)
private final MutableLiveData<OrderState> currentState = new MutableLiveData<>();
public Order(String productName, double price) {
this.orderId = generateOrderId();
this.productName = productName;
this.price = price;
this.createTime = new Date();
// 初始状态:待支付
this.currentState.setValue(OrderStateManager.getPendingPayState());
Log.d(TAG, "创建新订单: " + orderId);
}
// 生成唯一订单ID
private String generateOrderId() {
return "ORD" + System.currentTimeMillis() + UUID.randomUUID().toString().substring(0, 6);
}
// 对外暴露的操作方法(委托给当前状态)
public void pay(Context context) {
OrderState state = currentState.getValue();
if (state != null) {
state.handlePay(this, context);
}
}
public void cancel(Context context) {
OrderState state = currentState.getValue();
if (state != null) {
state.handleCancel(this, context);
}
}
public void ship(String logisticsNo) {
OrderState state = currentState.getValue();
if (state != null) {
state.handleShip(this, logisticsNo);
}
}
public void receive(Context context) {
OrderState state = currentState.getValue();
if (state != null) {
state.handleReceive(this, context);
}
}
public void refund(Context context) {
OrderState state = currentState.getValue();
if (state != null) {
state.handleRefund(this, context);
}
}
// 状态切换(由具体状态类调用)
public void setState(OrderState newState) {
Log.d(TAG, "订单状态变更: " + currentState.getValue().getStateName() +
" -> " + newState.getStateName());
currentState.setValue(newState);
}
// Getters
public String getOrderId() { return orderId; }
public String getProductName() { return productName; }
public double getPrice() { return price; }
public Date getCreateTime() { return createTime; }
public String getLogisticsNo() { return logisticsNo; }
public Date getPayTime() { return payTime; }
public Date getShipTime() { return shipTime; }
public Date getReceiveTime() { return receiveTime; }
public MutableLiveData<OrderState> getCurrentState() { return currentState; }
// Setters(仅允许状态类设置)
public void setLogisticsNo(String logisticsNo) { this.logisticsNo = logisticsNo; }
public void setPayTime(Date payTime) { this.payTime = payTime; }
public void setShipTime(Date shipTime) { this.shipTime = shipTime; }
public void setReceiveTime(Date receiveTime) { this.receiveTime = receiveTime; }
public void setCancelTime(Date cancelTime) { this.cancelTime = cancelTime; }
public void setRefundTime(Date refundTime) { this.refundTime = refundTime; }
}
2. 具体状态实现
PendingPayState.java(待支付状态)
package com.example.orderdemo.data.state;
import android.content.Context;
import android.widget.Toast;
import com.example.orderdemo.data.model.Order;
import com.example.orderdemo.data.model.OrderState;
import com.example.orderdemo.util.OrderStateManager;
import java.util.Date;
/**
* 待支付状态
*/
public class PendingPayState implements OrderState {
// 单例实例
private static final PendingPayState INSTANCE = new PendingPayState();
private PendingPayState() {}
public static PendingPayState getInstance() {
return INSTANCE;
}
@Override
public void handlePay(Order order, Context context) {
// 模拟支付流程
Toast.makeText(context, "正在发起支付...", Toast.LENGTH_SHORT).show();
// 模拟网络请求延迟
new Thread(() -> {
try {
Thread.sleep(1500); // 模拟支付处理时间
// 支付成功,切换到已支付状态
order.setPayTime(new Date());
order.setState(OrderStateManager.getPaidState());
// 在UI线程显示结果
((android.app.Activity) context).runOnUiThread(() ->
Toast.makeText(context, "支付成功!", Toast.LENGTH_SHORT).show());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Override
public void handleCancel(Order order, Context context) {
order.setCancelTime(new Date());
order.setState(OrderStateManager.getCancelledState());
Toast.makeText(context, "订单已取消", Toast.LENGTH_SHORT).show();
}
@Override
public void handleShip(Order order, String logisticsNo) {
// 待支付状态不支持发货
}
@Override
public void handleReceive(Order order, Context context) {
Toast.makeText(context, "请先完成支付", Toast.LENGTH_SHORT).show();
}
@Override
public void handleRefund(Order order, Context context) {
Toast.makeText(context, "未支付订单无法退款", Toast.LENGTH_SHORT).show();
}
@Override
public String getStateName() {
return "待支付";
}
@Override
public String getStateCode() {
return "PENDING_PAY";
}
@Override
public int[] getAvailableActions() {
// 待支付状态下可用的操作:支付、取消
return new int[]{Action.PAY, Action.CANCEL};
}
}
PaidState.java(已支付状态)
package com.example.orderdemo.data.state;
import android.content.Context;
import android.widget.Toast;
import com.example.orderdemo.data.model.Order;
import com.example.orderdemo.data.model.OrderState;
import com.example.orderdemo.util.OrderStateManager;
import java.util.Date;
import java.util.Random;
/**
* 已支付状态
*/
public class PaidState implements OrderState {
private static final PaidState INSTANCE = new PaidState();
private PaidState() {}
public static PaidState getInstance() {
return INSTANCE;
}
@Override
public void handlePay(Order order, Context context) {
Toast.makeText(context, "已完成支付,无需重复支付", Toast.LENGTH_SHORT).show();
}
@Override
public void handleCancel(Order order, Context context) {
Toast.makeText(context, "已支付订单请申请退款", Toast.LENGTH_SHORT).show();
}
@Override
public void handleShip(Order order, String logisticsNo) {
// 生成物流单号(模拟商家操作)
String logNo = logisticsNo != null ? logisticsNo : generateLogisticsNo();
order.setLogisticsNo(logNo);
order.setShipTime(new Date());
order.setState(OrderStateManager.getShippedState());
}
@Override
public void handleReceive(Order order, Context context) {
Toast.makeText(context, "商家尚未发货,无法确认收货", Toast.LENGTH_SHORT).show();
}
@Override
public void handleRefund(Order order, Context context) {
Toast.makeText(context, "正在处理退款...", Toast.LENGTH_SHORT).show();
new Thread(() -> {
try {
Thread.sleep(2000);
order.setRefundTime(new Date());
order.setState(OrderStateManager.getCancelledState());
((android.app.Activity) context).runOnUiThread(() ->
Toast.makeText(context, "退款成功", Toast.LENGTH_SHORT).show());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 生成模拟物流单号
private String generateLogisticsNo() {
return "SF" + new Random().nextInt(1000000000);
}
@Override
public String getStateName() {
return "已支付";
}
@Override
public String getStateCode() {
return "PAID";
}
@Override
public int[] getAvailableActions() {
// 已支付状态下可用的操作:查看物流(模拟)、申请退款
return new int[]{Action.VIEW_LOGISTICS, Action.REFUND};
}
}
(其他状态类 ShippedState.java、ReceivedState.java、CancelledState.java 实现类似,略)
3. 状态管理器与操作常量
OrderStateManager.java(状态管理器)
package com.example.orderdemo.util;
import com.example.orderdemo.data.model.OrderState;
import com.example.orderdemo.data.state.CancelledState;
import com.example.orderdemo.data.state.PaidState;
import com.example.orderdemo.data.state.PendingPayState;
import com.example.orderdemo.data.state.ReceivedState;
import com.example.orderdemo.data.state.ShippedState;
/**
* 订单状态管理器:提供统一的状态实例访问
*/
public class OrderStateManager {
// 所有状态实例
private static final OrderState PENDING_PAY = PendingPayState.getInstance();
private static final OrderState PAID = PaidState.getInstance();
private static final OrderState SHIPPED = ShippedState.getInstance();
private static final OrderState RECEIVED = ReceivedState.getInstance();
private static final OrderState CANCELLED = CancelledState.getInstance();
// 根据状态编码获取状态实例(用于从网络/数据库恢复)
public static OrderState getStateByCode(String code) {
switch (code) {
case "PAID":
return PAID;
case "SHIPPED":
return SHIPPED;
case "RECEIVED":
return RECEIVED;
case "CANCELLED":
return CANCELLED;
default: // 默认待支付
return PENDING_PAY;
}
}
// 直接获取特定状态
public static OrderState getPendingPayState() { return PENDING_PAY; }
public static OrderState getPaidState() { return PAID; }
public static OrderState getShippedState() { return SHIPPED; }
public static OrderState getReceivedState() { return RECEIVED; }
public static OrderState getCancelledState() { return CANCELLED; }
}
Action.java(操作常量)
package com.example.orderdemo.data.model;
/**
* 订单支持的操作类型
*/
public class Action {
// 操作常量
public static final int PAY = 1; // 支付
public static final int CANCEL = 2; // 取消
public static final int VIEW_LOGISTICS = 3; // 查看物流
public static final int RECEIVE = 4; // 确认收货
public static final int REFUND = 5; // 申请退款
// 操作名称映射
public static String getActionName(int action) {
switch (action) {
case PAY: return "去支付";
case CANCEL: return "取消订单";
case VIEW_LOGISTICS: return "查看物流";
case RECEIVE: return "确认收货";
case REFUND: return "申请退款";
default: return "";
}
}
}
4. ViewModel 与 UI 实现
OrderViewModel.java
package com.example.orderdemo.ui;
import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import com.example.orderdemo.data.model.Order;
/**
* 订单视图模型:管理订单数据,处理业务逻辑
*/
public class OrderViewModel extends AndroidViewModel {
private Order currentOrder;
public OrderViewModel(@NonNull Application application) {
super(application);
// 模拟创建一个订单
currentOrder = new Order("无线蓝牙耳机", 299.00);
// 模拟商家发货(实际场景由服务器触发)
simulateShipAfterDelay();
}
// 获取当前订单
public Order getCurrentOrder() {
return currentOrder;
}
// 模拟支付后一段时间商家发货
private void simulateShipAfterDelay() {
new Thread(() -> {
try {
Thread.sleep(30000); // 30秒后模拟发货
if (currentOrder != null &&
"PAID".equals(currentOrder.getCurrentState().getValue().getStateCode())) {
currentOrder.ship(null); // 自动发货
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
// 模拟超时未支付自动取消
public void startPaymentTimeoutCheck() {
new Thread(() -> {
try {
Thread.sleep(60000); // 60秒后检查
if (currentOrder != null &&
"PENDING_PAY".equals(currentOrder.getCurrentState().getValue().getStateCode())) {
currentOrder.cancel(getApplication()); // 自动取消
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
OrderActivity.java
package com.example.orderdemo.ui;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.example.orderdemo.R;
import com.example.orderdemo.data.model.Action;
import com.example.orderdemo.data.model.Order;
import com.example.orderdemo.data.model.OrderState;
import java.text.SimpleDateFormat;
public class OrderActivity extends AppCompatActivity {
private OrderViewModel orderViewModel;
private Order currentOrder;
private TextView tvOrderId;
private TextView tvProduct;
private TextView tvPrice;
private TextView tvState;
private TextView tvTime;
private LinearLayout llActions;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_order);
// 初始化视图
initViews();
// 获取ViewModel
orderViewModel = new ViewModelProvider(this).get(OrderViewModel.class);
currentOrder = orderViewModel.getCurrentOrder();
updateOrderInfo();
// 观察状态变化
observeStateChanges();
// 启动支付超时检查
orderViewModel.startPaymentTimeoutCheck();
}
private void initViews() {
tvOrderId = findViewById(R.id.tv_order_id);
tvProduct = findViewById(R.id.tv_product);
tvPrice = findViewById(R.id.tv_price);
tvState = findViewById(R.id.tv_state);
tvTime = findViewById(R.id.tv_time);
llActions = findViewById(R.id.ll_actions);
}
private void observeStateChanges() {
currentOrder.getCurrentState().observe(this, state -> {
// 状态变化时更新UI
updateOrderInfo();
updateActionButtons(state);
});
}
// 更新订单基本信息
private void updateOrderInfo() {
tvOrderId.setText("订单编号: " + currentOrder.getOrderId());
tvProduct.setText("商品: " + currentOrder.getProductName());
tvPrice.setText("金额: ¥" + currentOrder.getPrice());
tvState.setText("当前状态: " + currentOrder.getCurrentState().getValue().getStateName());
tvTime.setText("创建时间: " + sdf.format(currentOrder.getCreateTime()));
}
// 根据当前状态更新操作按钮
private void updateActionButtons(OrderState state) {
llActions.removeAllViews();
int[] actions = state.getAvailableActions();
for (int action : actions) {
Button button = new Button(this);
button.setText(Action.getActionName(action));
button.setOnClickListener(v -> handleAction(action));
// 添加按钮到布局
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1);
params.setMargins(8, 8, 8, 8);
llActions.addView(button, params);
}
}
// 处理用户操作
private void handleAction(int action) {
switch (action) {
case Action.PAY:
currentOrder.pay(this);
break;
case Action.CANCEL:
currentOrder.cancel(this);
break;
case Action.VIEW_LOGISTICS:
String logisticsNo = currentOrder.getLogisticsNo();
if (logisticsNo != null) {
Toast.makeText(this, "物流单号: " + logisticsNo, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(this, "商家尚未发货", Toast.LENGTH_SHORT).show();
}
break;
case Action.RECEIVE:
currentOrder.receive(this);
break;
case Action.REFUND:
currentOrder.refund(this);
break;
}
}
}
activity_order.xml(布局文件)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="订单详情"
android:textSize="20sp"
android:textStyle="bold"/>
<TextView
android:id="@+id/tv_order_id"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_product"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_price"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="@color/design_default_color_error"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_state"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="16sp"/>
<TextView
android:id="@+id/tv_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textSize="14sp"
android:textColor="@android:color/darker_gray"/>
<LinearLayout
android:id="@+id/ll_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:orientation="horizontal"/>
</LinearLayout>
实现说明
核心设计亮点
- 状态与 UI 解耦
- 使用 LiveData 观察状态变化,状态变更时自动更新 UI
- 操作按钮根据当前状态动态生成,无需在布局中预先定义所有按钮
- 符合 Android 生命周期
- 订单数据通过 ViewModel 管理,旋转屏幕等配置变化时数据不丢失
- 耗时操作(如支付、退款)在子线程执行,避免阻塞 UI
- 业务逻辑封装
- 每个状态的行为约束和状态切换逻辑封装在对应状态类中
- 状态管理器统一管理状态实例,避免重复创建和状态不一致
- 可扩展性
- 新增状态只需实现 OrderState 接口并注册到状态管理器
- 新增操作只需在 Action 类中添加常量,在对应状态类中实现逻辑
状态流转测试流程
- 打开订单详情页,初始状态为 "待支付",显示 "去支付" 和 "取消订单" 按钮
- 点击 "去支付",模拟支付成功后状态变为 "已支付"
- 30 秒后自动模拟商家发货,状态变为 "已发货"
- 点击 "确认收货",状态变为 "已签收"
- 点击 "申请退款",状态变为 "已取消"
扩展建议
- 集成真实支付 SDK 在 PendingPayState 的 handlePay 方法中集成微信 / 支付宝支付 SDK,实现真实支付流程
- 添加网络同步 状态变更时通过 Retrofit 等网络库同步到服务器,确保客户端与服务端状态一致
- 完善物流跟踪 在 ShippedState 中添加物流状态查询,通过 API 获取实时物流信息
- 添加状态历史记录 订单类中添加状态变更历史列表,记录每次状态变更的时间和操作人
- 实现订单列表 扩展 ViewModel 管理多个订单,使用 RecyclerView 展示订单列表,每个订单独立管理自己的状态
这个实例完整展示了状态模式在 Android 客户端的应用,通过合理的架构设计实现了订单状态的灵活管理和 UI 的实时响应,同时保持了代码的可维护性和可扩展性。
总结
状态模式是解决 “状态驱动行为” 问题的优秀方案,其核心是 “用对象封装状态,用委托替代判断”:
- 核心价值:
- 消除冗余
if-else,提升代码可读性和可维护性; - 状态与行为强绑定,符合单一职责和开闭原则;
- 适合状态较多且变化频繁的场景(如订单、播放器)。
- 消除冗余
- 使用建议:
- 状态少(2-3 种)且逻辑简单时,优先用
if-else(避免过度设计); - 状态多且逻辑复杂时,用状态模式(推荐结合单例和状态管理器);
- 分布式场景需注意状态持久化(如将状态存数据库,恢复时通过状态管理器加载)。
- 状态少(2-3 种)且逻辑简单时,优先用
- Android 开发启示:
- 自定义 View 或组件时,可用状态模式管理复杂状态(如加载中、成功、失败);
- 理解
MediaPlayer等系统组件的状态逻辑,避免因状态错误导致崩溃; - 复杂业务流程(如支付流程)可结合 Spring StateMachine 等框架简化开发。
状态模式不是 “万能药”,需根据业务场景权衡其优缺点,才能写出简洁、健壮的代码。