状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式,把特定于状态的代码抽象到一组独立的状态类中避免过多的状态条件判断,减少维护成本。即使用Switch-case会有复杂if-else逻辑和大量代码冗余,把不同case抽离出不同的类即对应方法。
主要由环境类角色、抽象状态角色和具体状态角色,三个角色构成。
- Context(环境类):环境类又称为上下文类,它定义客户端需要的接口,内部维护一个当前状态实例,并负责具体状态的切换。
- State(抽象状态):定义状态下的行为,可以有一个或多个行为。
- ConcreteState(具体状态):每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。
下面用 Golang 实现状态模式来解构红绿灯在不同灯的状态下所具有的行为。
首先针对交通红绿灯,每种灯状态下都有亮灯、变灯、测速的行为,首先定义出交通灯的状态接口。
go
// State interface
type LightState interface {
// 亮起当前状态的交通灯
Light()
// 转换到新状态的时候,调用的方法
EnterState()
// 设置一个状态要转变的状态
NextLight(light *TrafficLight)
// 检测车速
CarPassingSpeed(*TrafficLight, int, string)
}
然后定义环境类 Context,它提供客户端调用状态行为的接口。
go
// Context
type TrafficLight struct {
State LightState
SpeedLimit int
}
func NewSimpleTrafficLight(speedLimit int) *TrafficLight {
return &TrafficLight{
SpeedLimit: speedLimit,
State: NewRedState(),
}
}
先定义一个DefaultLightState
类型用于让具体的LightState
嵌套组合,减少公用法在每个具体 LightState
实现类中的重复实现。
go
type DefaultLightState struct {
StateName string
}
func (state *DefaultLightState) CarPassingSpeed(road *TrafficLight, speed int, licensePlate string) {
if speed > road.SpeedLimit {
fmt.Printf("Car with license %s was speeding\n", licensePlate)
}
}
func (state *DefaultLightState) EnterState(){
fmt.Println("changed state to:", state.StateName)
}
func (tl *TrafficLight) TransitionState(newState LightState) {
tl.State = newState
tl.State.EnterState()
}
定义三个具体状态类型,去实现LightState
接口,首先是红灯的状态实现。
go
接下来我们定义三个具体状态类型,去实现LightState接口,首先是红灯的状态实现。
// 红灯状态
type redState struct {
DefaultLightState
}
func NewRedState() *redState {
state := &redState{}
state.StateName = "RED"
return state
}
func (state *redState) Light() {
fmt.Println("红灯亮起,不可行驶")
}
func (state *redState) CarPassingSpeed(light *TrafficLight, speed int, licensePlate string) {
// 红灯时不能行驶, 所以这里要重写覆盖 DefaultLightState 里定义的这个方法
if speed > 0 {
fmt.Printf("Car with license \"%s\" ran a red light!\n", licensePlate)
}
}
func (state *redState) NextLight(light *TrafficLight){
light.TransitionState(NewGreenState())
}
绿灯和黄灯状态
go
// 绿灯状态
type greenState struct{
DefaultLightState
}
func NewGreenState() *greenState{
state := &greenState{}
state.StateName = "GREEN"
return state
}
func (state *greenState) Light(){
fmt.Println("绿灯亮起,请行驶")
}
func (state *greenState) NextLight(light *TrafficLight){
light.TransitionState(NewAmberState())
}
// 黄灯状态
type amberState struct {
DefaultLightState
}
func NewAmberState() *amberState{
state := &amberState{}
state.StateName = "AMBER"
return state
}
func (state *amberState) Light(){
fmt.Println("黄灯亮起,请注意")
}
func (state *amberState) NextLight(light *TrafficLight){
light.TransitionState(NewRedState())
}
状态实现类在内部确定了状态可以转换的下个状态,这样就把系统流程的状态机留在了内部,避免让客户端代码再去做状态链初始化和转换的判断,符合高内聚的设计原则,从而解放了客户端。
go
func main() {
trafficLight := NewSimpleTrafficLight(500)
interval := time.NewTicker(5 * time.Second)
for {
select {
case <- interval.C:
trafficLight.State.Light()
trafficLight.State.CarPassingSpeed(trafficLight, 25, "CN1024")
trafficLight.State.NextLight(trafficLight)
default:
}
}
}
适用场景:
-
如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。
- 模式将所有特定于状态的代码抽取到一组独立的类中。 这样一来, 可以在独立于其他状态的情况下添加新状态或修改已有状态, 从而减少维护成本。
-
如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。
- 状态模式会将这些条件语句的分支抽取到相应状态类的方法中。 通过业务逻辑内聚,减少客户端类的这部分工作。
-
当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。
- 状态模式让你能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。
缺点:
- 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 状态模式对"开闭原则"的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。