本文深入解析JavaScript中的状态模式,阐述其作为行为型设计模式的核心原理:通过封装不同状态下的行为来避免复杂的条件判断。文章详细拆解了环境类、抽象状态类和具体状态类的组成结构,并通过对比代码示例展示了状态模式在简化逻辑、符合开闭原则方面的优势,同时指出了状态过多时可能带来的复杂性挑战。
状态模式(State Pattern)
这是一种行为型设计模式,其核心机制在于当对象内部状态发生变化时,对象的行为也随之改变。
该模式将对象在不同状态下的具体行为封装到独立的状态类中,从而使对象能够根据当前状态动态选择执行相应的行为逻辑。
组成
环境类:这是持有状态的对象,它在运行时将自身的操作委托给当前的状态对象执行。具体包括:①环境类维护一个指向抽象状态类的引用;②通过该引用来调用具体状态类中定义的方法。
抽象状态类:该类定义了一个接口③,用于封装与环境类特定状态相关的行为逻辑。
具体状态类:实现了抽象状态类定义的接口,具体描述了在某一特定状态下对象的行为表现④。
工作流程
环境类通过持有一个抽象状态类的引用来标识当前的状态。
客户端通过调用环境类的方法触发某个操作。
在运行过程中,环境类会将该操作委托给当前持有的状态对象进行处理。
当对象的状态发生转换时,环境类会更新当前状态对象的引用,从而实现对象行为的改变。
趣例
以下示例展示了一位西安老铁观看国足比赛时心理状态的变化过程:
abstract class State {
constructor(public context: Context | null = null) {}
// 这里体现出③:用于封装与环境类的特定状态相关的行为
handle() {
if (!this.context) {
console.log("找不到老铁了!");
} else {
this.exec();
}
}
next<T extends State>(NextState: new (context: Context) => T) {
if (!this.context) {
console.log("找不到老铁了!");
} else {
this.context.to(new NextState(this.context));
}
}
abstract exec(): any;
}
// 具体状态类
class StateA extends State {
// 这里体现出④:用于封装与环境类的特定状态相关的行为
exec() {
console.log("买票");
this.next(StateB);
}
}
class StateB extends State {
exec() {
console.log("坐车");
this.next(StateC);
}
}
class StateC extends State {
exec() {
console.log("看球");
this.next(StateO);
}
}
class StateO extends State {
exec() {
throw new Error('已经结束了');
}
}
// 环境类
class Context {
// 这里体现出①:环境类维护一个对抽象状态类的引用,即cts
constructor(public cts: State | null = null) {}
to(state: State) {
console.log(`Transitioning to ${state.constructor.name}`);
this.cts = state;
}
do() {
// 这里体现出②:通过该引用来调用具体状态类的方法
this.cts?.handle();
}
}
// 使用示例
const context = new Context();
const stateA = new StateA(context);
context.to(stateA);
while(1){
try {
context.do();
} catch {
console.log('肯定输了!');
break;
}
}对照例子理解
- 状态模式:一种行为型设计模式,核心是①【对象】在其②【内部状态】③【改变】时④【改变其行为】。
①对象:指代环境类对象,即上述代码中的context对象。
②内部状态:指context引用的状态类实例,例如stateA以及NextState的匿名实例。
③改变:指context.to方法的调用过程。
④行为:指context.do函数的执行结果。
改变其行为:指当context.cts引用不同的状态类实例时,context.do的执行结果有所不同。
状态模式将对象的行为封装到不同的状态类中,使得对象在不同状态下可以选择不同的行为。
这句话理解成:
原本需要在context.do中通过if-else实现的功能,被封装到了不同的状态类中(StateA、StateB);
当context的状态发生改变时(即:context.cts引用不同的状态类实例时),context.do也能实现封装前相同的功能(即表现出不同的行为)。
见下面代码:
不使用状态设计模式实现上述代码功能:
class Context {
state = 'A';
to(state: string) {
console.log(`Transitioning to state ${state}`);
this.state = state;
}
do() {
if(this.state === 'A'){
console.log("买票");
this.to('B');
} else if (this.state === 'B') {
console.log("坐车");
this.to('C');
} else if (this.state === 'C') {
console.log("看球");
this.to('D');
} else if (this.state === 'D') {
throw new Error('已经结束了');
}
}
}
const context = new Context();
while(1){
try {
context.do();
} catch {
console.log('肯定输了!');
break;
}
}【对比】虽然这样看起来代码量好像少了一些,但是如果老铁想要在下车后去趟卫生间,使用了状态设计模式的代码只需要修改StateB的实现就可以了,而后者则必须修改Context中的do方法!
优点
封装之后的状态可以独立变化,符合开闭原则。
状态模式提供了一种清晰的方式来组织对象的行为,并避免了使用大量的条件语句。
缺点
状态较多时,导致状态类的数量增加,增加系统的复杂性。
对象在不同状态下的转换逻辑可能会分散在多个状态类中,使得代码难以理解和维护。
如果某个状态只被一个对象使用,可以考虑使用策略模式替代状态模式。
应用
状态模式常见于需要根据对象的内部状态来改变其行为的场景,例如【订单状态的处理】、【游戏角色的状态转换】等。
它提供了一种可扩展的方式来处理状态相关的逻辑,并促进了代码的灵活性和可维护性。
generator函数可以看成原生js实现了状态设计模式。
感性记忆
同一个对象,当其内部维护的状态不同的时候,调用其上的同一个方法,可能会得到完全不同的结果。
总结
就是适用于同一个对象具有不同的状态,并且在各个状态下表现不一致的情况;这些状态之间如果是可以相互转换的,使用此设计模式就更加合适了。
到此这篇关于JS设计模式之状态模式的用法使用方法的文章就介绍到这了,更多相关JS状态模式使用内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!