初识设计模式——状态模式(State Pattern)

cuixiaogang

状态模式(State Pattern)属于行为设计模式,其核心在于允许对象在内部状态发生改变时变更自身的行为,仿佛对象更改了所属的类。该模式将状态相关的行为封装在不同的状态类中,并且让对象在运行时可以动态切换状态。

状态模式的构成

  • 上下文(Context):它持有一个状态对象的引用,定义了客户端感兴趣的接口,并且负责状态的切换。
  • 抽象状态(State):定义了一个所有具体状态类都要实现的接口,通常包含了与上下文相关的行为方法。
  • 具体状态(Concrete State):实现了抽象状态接口,每个具体状态类负责处理特定状态下的行为。

状态模式结构图

案例

场景

在 WEB 开发中,一个常见的应用场景是用户账户的状态管理。用户账户可能有多种状态,如 “正常”、“冻结”、“锁定” 等,不同状态下用户的操作权限不同。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?php
// 抽象状态类
abstract class AccountState {
protected $account;

public function __construct(Account $account) {
$this->account = $account;
}

abstract public function login();
abstract public function logout();
abstract public function freeze();
abstract public function unlock();
}

// 正常状态类
class NormalState extends AccountState {
public function login() {
echo "用户正常登录\n";
}

public function logout() {
echo "用户正常退出\n";
}

public function freeze() {
echo "用户账户被冻结\n";
$this->account->setState(new FrozenState($this->account));
}

public function unlock() {
echo "用户账户已经是正常状态,无需解锁\n";
}
}

// 冻结状态类
class FrozenState extends AccountState {
public function login() {
echo "用户账户已冻结,无法登录\n";
}

public function logout() {
echo "用户账户已冻结,无需退出\n";
}

public function freeze() {
echo "用户账户已经是冻结状态,无需再次冻结\n";
}

public function unlock() {
echo "用户账户已解锁\n";
$this->account->setState(new NormalState($this->account));
}
}

// 上下文类:用户账户
class Account {
private $state;

public function __construct() {
$this->state = new NormalState($this);
}

public function setState(AccountState $state) {
$this->state = $state;
}

public function login() {
$this->state->login();
}

public function logout() {
$this->state->logout();
}

public function freeze() {
$this->state->freeze();
}

public function unlock() {
$this->state->unlock();
}
}

// 测试代码
$account = new Account();
$account->login();
$account->freeze();
$account->login();
$account->unlock();
$account->login();
?>

代码解释

  • AccountState 是抽象状态类,定义了用户账户的基本操作接口。
  • NormalState 和 FrozenState 是具体状态类,分别实现了正常状态和冻结状态下的用户操作。
  • Account 是上下文类,持有一个状态对象的引用,并提供了调用状态对象方法的接口。

UML类图

UML类图

状态模式的适用场景

  • 对象行为依赖状态:当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变行为时,适合使用状态模式。
  • 条件语句复杂:当代码中存在大量的条件语句(如 if-else 或 switch-case)来根据对象的状态执行不同的操作时,使用状态模式可以将这些条件逻辑分散到各个状态类中,使代码更易于维护和扩展。

状态模式的优缺点

优点

  • 可维护性高:将状态相关的行为封装在不同的状态类中,避免了大量的条件语句,使代码结构更加清晰,易于维护。
  • 可扩展性强:当需要添加新的状态时,只需创建一个新的具体状态类并实现相应的行为,而不需要修改现有的代码,符合开闭原则。
  • 提高代码可读性:每个状态类只负责处理自己的行为,使代码的意图更加明确,提高了代码的可读性。

缺点

  • 类的数量增加:引入状态模式会导致类的数量增加,特别是当状态较多时,会使系统变得复杂。
  • 状态转换逻辑复杂:如果状态之间的转换逻辑复杂,可能会导致状态类之间的耦合度增加,增加了代码的理解和维护难度。

状态模式在实际应用中需要注意的问题

设计层面

  1. 状态的合理划分
    要准确识别出对象的各种状态,状态划分不能过于琐碎或笼统。如果状态划分过于细致,会导致状态类数量过多,增加系统复杂度;若划分过于宽泛,可能无法精确描述对象在不同情况下的行为,失去状态模式的意义。例如,在电商订单系统中,订单状态可划分为“待支付”“已支付”“已发货”“已签收”等常见状态,若再细分出“支付中”“发货准备中”等状态,需根据实际业务需求权衡是否必要。

  2. 状态转换逻辑的处理
    状态之间的转换逻辑要清晰明确,且应封装在合适的位置。通常状态转换逻辑可放在上下文类或者具体状态类中。若转换逻辑较为简单且固定,可在上下文类中处理;若转换逻辑与具体状态密切相关,则可放在具体状态类中。例如,在游戏角色状态管理中,角色从“正常”状态到“受伤”状态的转换可能由受到攻击这一事件触发,转换逻辑可根据具体情况放在合适的类中。

  3. 抽象状态接口的设计
    抽象状态接口应定义合理且全面的方法,这些方法要涵盖对象在不同状态下可能执行的操作。接口设计过于复杂会增加具体状态类的实现难度,过于简单则可能无法满足业务需求。例如,在文档状态管理系统中,抽象状态接口可能包含“保存”“提交审核”“发布”等方法。

实现层面

  1. 状态对象的创建和管理
    状态对象的创建方式和管理策略需要合理规划。可采用单例模式来创建状态对象,以避免频繁创建和销毁对象带来的性能开销。同时,要确保状态对象的生命周期管理得当,避免出现内存泄漏等问题。例如,在一个长时间运行的系统中,若状态对象频繁创建而不及时释放,会占用大量内存资源。

  2. 上下文类与状态类的耦合度
    虽然状态模式旨在降低对象状态和行为之间的耦合度,但上下文类和状态类之间仍存在一定的耦合。要尽量减少这种耦合,可通过依赖注入等方式实现。例如,在上下文类中通过构造函数或方法参数传入状态对象,而不是在上下文类内部直接创建状态对象。

  3. 异常处理
    在状态转换和状态相关操作过程中,要考虑可能出现的异常情况,并进行适当的处理。例如,当状态转换条件不满足时,应给出明确的错误信息,避免程序崩溃。在文件状态管理系统中,若尝试将一个已删除的文件状态转换为“编辑中”,应抛出相应的异常并提示用户。

维护和扩展层面

  1. 代码的可读性和可维护性
    随着系统的发展,状态类和状态转换逻辑可能会变得复杂。要确保代码具有良好的可读性和可维护性,可通过添加注释、合理命名类和方法等方式实现。例如,在状态类和方法命名时,使用具有描述性的名称,让其他开发者能够快速理解代码的功能。

  2. 新状态的添加和修改
    当需要添加新的状态或修改现有状态时,要遵循开闭原则,尽量减少对现有代码的修改。例如,添加新状态时,只需创建一个新的具体状态类并实现抽象状态接口,同时在上下文类中添加相应的状态转换逻辑。

  3. 与其他设计模式的结合使用
    在实际应用中,状态模式可能需要与其他设计模式结合使用,以满足更复杂的业务需求。例如,与策略模式结合,可根据不同的状态选择不同的算法;与观察者模式结合,可在状态发生变化时通知相关的对象。