正文
这就是多态在起作用:一个方法调用,根据具体的状态
对象
不同,表现出不同的行为。
避免条件语句过于复杂
使用状态模式的主要动机之一,就是为了消除代码中重复且分散的条件逻辑。如果一个对象的行为会根据状态变化而改变,你可能会倾向于用枚举或布尔标志来跟踪状态,然后在每个需要根据状态处理的地方写
switch
或
if
语句。这种做法会让代码变得臃肿、难以维护。
状态模式的做法是把每种状态下的逻辑封装在独立的类中:
-
每种状态的逻辑都放在自己专属的类中(比如 “静音模式” 的所有逻辑都放在
SilentState
里)。
-
上下文(Context)对象的代码会变得更简单,不再需要处理大段的条件判断逻辑。
-
增加新的状态或修改已有状态,不需要在多个地方修改庞大的
switch
语句 —— 只需新增或修改一个状态类。
经典定义中提到:“当某些操作包含大量依赖于对象状态的条件语句时,状态模式会将每个条件分支封装在独立的类中,把状态当作一个独立的对象来看待。”
这种封装方式符合开闭原则(Open/Closed Principle):我们可以在不修改原有上下文或其他状态类的情况下引入新的状态。同时也符合单一职责原则(Single Responsibility Principle),因为每个状态类只负责处理一种状态下的行为。
什么时候该使用(或不使用)状态模式
适合使用状态模式的场景:
-
当一个对象的行为依赖其当前状态,并且它在运行时需要根据状态改变行为时。如果你发现自己在多个地方都写着 “如果状态是 X 做这个,状态是 Y 做那个”,那可能就适合用状态模式。
-
当一个对象有多个行为逻辑,并且这些逻辑可以明确地按状态划分。例如,手机的响铃、震动、静音记录等行为都可以独立处理。
-
想要避免状态判断逻辑重复出现在多个方法中。使用状态模式后,这些行为被集中封装在状态类中,不再重复。
-
预计未来可能会增加新的状态,或每个状态下的逻辑会变得更复杂。状态模式的结构更容易扩展(新增一个状态类)或修改(只需改动一个类的代码)。
不适合使用状态模式的情况或需谨慎使用:
-
如果对象只有一两个状态,而且每种状态下的行为差异非常简单,那么使用状态模式可能就有点小题大做了。用普通的条件判断反而更清晰。
-
如果状态切换很少发生,或者每种状态的逻辑基本不会变,那用状态模式引入的一堆类可能并不值得。
-
如果状态数量固定且逻辑简单明确,使用枚举加
switch
语句可能就足够了。状态模式适用于那些状态复杂且易变的场景。
可以这样想:一个只有两个状态的小状态机,用 if 来管理也没问题。但如果是一个有十种状态、状态之间还有复杂切换逻辑的状态机,那用状态模式结构化处理会更好维护。
为什么状态模式比枚举和标志变量更好?
一开始,很多人会选择用枚举或布尔标志来表示状态,比如:
enum Mode { normal, vibrate, silent }
然后用类似这样的逻辑处理行为:
if(mode == Mode.normal){
// 响铃
}else if(mode == Mode.vibrate){
// 震动
}else if(mode == Mode.silent){
// 保持静音
}
这种方式起初是可行的,但随着程序变复杂,会出现以下问题:
-
逻辑分散 - 如果多个行为都依赖状态判断,你就会在很多方法里看到类似的
if/else
或
switch
,例如
handleCall()
、
notifyMessage()
、
alarmRing()
等等。状态行为稍有改动,就得到处找这些条件语句并改动。
-
违反开闭原则 - 比如你想新增一个 “请勿打扰” 模式(Do Not Disturb),就得修改所有相关的
switch
语句。每次修改都有可能引入 bug,影响原有功能。
-
维护困难 - 状态和条件越来越多,代码就越难阅读和维护,容易变成一个嵌在业务逻辑中的 “巨型状态机”。
状态模式通过封装各个状态的行为,解决了这些问题。你不再需要一个大函数来处理各种分支,而是有多个小类,各自处理自己的状态行为。这样结构更清晰: