ADT(代数数据类型)是函数式语音中一种强大的语言特性,这次用它来实现状态模式
状态模式与策略模式存在某些相似性,它们都可以实现某种算法、业务逻辑的切换。以下是状态模式的定义:
状态模式允许一个对象在其内部状态改变时改变的行为,对象看起来似乎修改了它的类。
状态模式具体表现在:
- 状态决定行为,对象的行为由它内部的状态决定
- 对象的状态在运行期被改变时,它的行为也会因此而改变。从表面上看,同一个对象,在不同的运行时刻,行为是不一样的,就像是类被修改了一样
再次与策略模式做对比,策略模式通过在客户端切换不同的策略实现来改变算法;而在状态模式中,对象通过修改内部的状态来切换不同的行为方法。
现在我们举一个饮水机的例子,假如一个饮水机有3种工作状态,分别为未启动、制冷模式、制热模式,那么可以用密封类来封装一个代表不同饮水机状态的ADT。
sealed class WaterMachineState(open val machine: WaterMachine) {
fun turnHeating() {
if (this !is Heating) {
print("turn heating")
machine.state = machine.heating
} else {
print("The state is already heating mode.")
}
}
fun turnCooling() {
if (this !is Cooling) {
print("turn cooling")
machine.state = machine.cooling
} else {
print("The state is already cooling mode.")
}
}
fun turnOff() {
if (this !is Off) {
print("turn off")
machine.state = machine.off
} else {
print("The state is already off mode.")
}
}
}
class Off(override val machine: WaterMachine) : WatermachineState(machine)
class Heating(override val machine: WaterMachine) : WatermachineState(machine)
class Cooling(override val machine: WaterMachine) : WatermachineState(machine)
以上代码分析:
- WatermachineState是一个密封类,拥有一个构造参数为WaterMachine类对象
- 在WatermachineState类外部我们分别定义了Off,Heating,Cooling来代表饮水机的3种不同的工作状态,它们都继承了WaterMachineState类的machine成员属性及3个状态切换的方法
- 在每个切换状态的方法中,我们通过改变machine对象的state,来实现切换饮水机状态的目的
接着看WaterMachine类:
class WaterMachine {
var state: WaterMachineState
val off = Off(this)
val heating = Heating(this)
val cooling = Cooling(this)
init {
this.state = off
}
fun turnHeating() {
this.state.turnHeating()
}
fun turnCooling() {
this.state.turnCooling()
}
fun turnOff() {
this.state.turnOff()
}
}
WaterMachine很简单,内部主要包含了一下成员属性和方法:
- 引用可变的WaterMachineState类对象state,用来表示当前饮水机所处的工作状态
- 分别表示3种不同状态的成员属性,off、heating、cooling,它们也是WaterMahineState类的3种子类对象;它们通过传入this进行构造,从而实现在WaterMachineState状态类内部,改变WaterMachine类的state引用值;当WaterMachine类对象初始化时,state默认为off,即饮水机处于未启动状态
- 3个直接调用的饮水机操作方法,分别执行对应state对象的3种操作,供客户端调用
如果办公室的小伙伴都喜欢喝冷水,早上一来就会把饮水机调整为制冷模式,但Shaw有吃泡面的习惯,他想泡面的时候,就会把饮水机变为制热,所以每次他吃了泡面,下一个喝水的同事就需要再切换回制冷。最后要下班了,Kim就会关闭饮水机的电源。
为了满足这一需求,我们设计了一个waterMachineOps函数:
enum class Moment {
EARLY_MORNING,
DRINKING_WATER,
INSTANCE_NOODLES,
AFTER_WORK
}
fun waterMachineOps(machine: WaterMachine,moment: Moment) {
when(moment) {
EARLY_MORNING, DRINKING_WATER -> when(machine.state) {
!is WatermachineState.Cooling ->machine.turnCooling()
}
INSTANCE_NOODLES -> when(machine.state) {
!is WatermachineState.Heating -> machine.turnHeating()
}
AFTER_WORK -> when(machine.state) {
!is WatermachineState.Off -> machine.turnOff()
}
}
}
这个方法很好地处理了不同角色在不同需求场景下,应该对饮水机执行的不同操作。此外,当用when表达式处理枚举类时,默认的情况必须用else进行处理。然而,由于密封类在类型安全上的额外设计,我们在处理machine对象的state对象时,则不需要考虑这一细节,在语言表达上要简洁得多。