设计模式-状态模式
学习一下「状态模式」
从一个场景切入
💡 需求:按下按钮,根据当前的状态,电灯会切换到新的状态 off->on/on->off
针对于这个小的需求,第一时间可能会写下这段代码
enum State {
on,
off,
}
class Light {
state: State;
constructor() {
this.state = State.off;
}
press() {
if (this.state === State.on) {
this.state = State.off;
} else if (this.state === State.off) {
this.state = State.on;
}
}
}
测试一下
const light = new Light();
console.assert(light.state === State.off);
light.press();
console.assert(light.state === State.on);
light.press();
console.assert(light.state === State.off);
看起来实现了功能,但其实是存在一些隐患(缺陷)的:一段时间后,需求变了,电灯增加了一种状态--强光:在关闭的情况下,第一次打开是正常光,再按一下会切换到强光,再按一下才是关闭。针对于这个变动,我们上面的代码稍作改动也能处理:
// ...
press() {
if (this.state === State.off) {
this.state = State.on;
} else if (this.state === State.on) {
// 打开的情况下再次按下会切换到强光
this.state = State.superLight;
} else if (this.state === State.superLight) {
this.state = State.off;
}
}
// ...
但这仅仅是增加了一种状态,那如果以后再添加一种弱光,强强光。。。那都要去修改 press
方法,一来该函数将会变得极其臃肿;再者,该设计也违反了开闭原则:每次新增或修改 light 的状态,都需要修改 press
方法,这使得该函数非常不稳定,难以维护
使用状态模式改进 💡 程序
状态模式的关键是把事物的每种状态都封装成单独的类,跟此种状态有关的行为都被封装在这个类的内部
class State {
light: Light;
constructor(light: Light) {
this.light = light;
}
press() {
throw new Error("press方法必须被重写");
}
}
class OffLightState extends State {
light: Light;
press() {
console.log("on");
this.light.setState(this.light.onLightState);
}
}
class OnLightState extends State {
light: Light;
press() {
console.log("superLight");
this.light.setState(this.light.superLightState);
}
}
class SuperLightState extends State {
light: Light;
press() {
console.log("off");
this.light.setState(this.light.offLightState);
}
}
class Light {
state: State;
offLightState: OffLightState;
superLightState: SuperLightState;
onLightState: OnLightState;
constructor() {
this.onLightState = new OnLightState(this);
this.offLightState = new OffLightState(this);
this.superLightState = new SuperLightState(this);
this.state = this.offLightState;
}
setState(state: State) {
this.state = state;
}
}
const light = new Light();
console.assert(light.state instanceof OffLightState);
light.state.press();
console.assert(light.state instanceof OnLightState);
light.state.press();
console.assert(light.state instanceof SuperLightState);
light.state.press();
console.assert(light.state instanceof OffLightState);
在这里,我们创建了几个不同的 State
,这个状态都有一个 press
方法,代表开关被按下时执行的动作:切换下一个状态
使用状态模式后,代码量明显增加,但这样的好处非常明显:它可以使每一种状态和它对应的行为之间的关系局部化,这些行为被分散和封装在各自对应的状态类中,便于管理;此外,由于状态之间的切换都被分布在状态类内部,因此我们无需编写过多的 if-else
来控制状态之间的转换
当我们需要为 light 新增一种状态时,只需要增加一个新的 State
然后改变相关联的状态之前的切换规则即可
使用状态机重构
借助于 xstate
重构 💡 需求
- 创建状态机
import { createMachine } from "xstate";
const lightMachine = createMachine({
id: "light",
initial: "off",
states: {
off: {
on: {
press: "weak",
},
},
weak: {
on: {
press: "normal",
},
},
normal: {
on: {
press: "super",
},
},
super: {
on: {
press: "off",
},
},
},
});
export { lightMachine };
- 使用
import { interpret } from "xstate";
import { lightMachine } from "./light.machine";
const lightService = interpret(lightMachine).onTransition(() => {
console.log("lightService.state: ", lightService.state.value);
});
lightService.start();
console.assert(lightService.state.value === "off");
lightService.send("press");
console.assert(lightService.state.value === "weak");
lightService.send("press");
console.assert(lightService.state.value === "normal");
lightService.send("press");
console.assert(lightService.state.value === "super");
lightService.send("press");
console.assert(lightService.state.value === "off");
总结
状态模式定义了状态与行为之间的关系,较为细致的区分了事物内部的状态,并且将跟此种状态相关的行为都封装在这个状态内部(比如切换到新的状态),避免了 Context 无限膨胀,方便增加新的状态和转换。但由于每个状态都需要一个类,较为繁琐,并且由于逻辑分散在各个状态类中,无形中增加了对整体的状态机逻辑的理解难度