likes
comments
collection
share

状态设计模式在Js中的使用

作者站长头像
站长
· 阅读数 14

状态模式(State Pattern)

一种行为型设计模式,核心是对象在其内部状态改变时改变其行为

状态模式将对象的行为封装到不同的状态类中,使得对象在不同状态下可以选择不同的行为。

组成

  1. 环境类:环境类是拥有状态的对象,它在运行时将自身的操作委托给当前的状态对象来处理。①环境类维护一个对抽象状态类的引用,②并通过该引用来调用具体状态类的方法。

  2. 抽象状态类:抽象状态类定义了一个接口③,用于封装与环境类的特定状态相关的行为。

  3. 具体状态类:具体状态类实现了抽象状态类定义的接口,它具体描述了在某个状态下对象的行为④。

工作流程

  1. 环境类通过持有一个抽象状态类的引用来表示当前的状态。

  2. 客户端通过调用环境类的方法来触发某个操作。

  3. 环境类在运行时将操作委托给当前状态对象进行处理。

  4. 当对象的状态发生改变时,环境类会更新当前状态对象的引用,从而改变对象的行为。

趣例

下面的这个例子展示了一位西安老铁观看国足比赛的时候的心理状态变化过程:


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;

  }

}

对照例子理解

  1. 状态模式:一种行为型设计模式,核心是①【对象】在其②【内部状态】③【改变】时④【改变其行为】
  • ①对象:指的是环境类对象,也就是上述代码中的context对象。

  • ②内部状态,指的是context引用的状态类实例,也就是stateA以及NextState的匿名实例。

  • ③改变:指的是context.to的调用。

  • ④行为:指的是context.do函数的执行结果。

  • 改变其行为:指的是context.cts引用不同的状态类实例的时候context.do的执行结果不同。

  1. 状态模式将对象的行为封装到不同的状态类中,使得对象在不同状态下可以选择不同的行为。

    这句话理解成:

    • 本来应该在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实现了状态设计模式。

感性记忆

同一个对象,当其内部维护的状态不同的时候,调用其上的同一个方法,可能会得到完全不同的结果。

总结

就是适用于同一个对象具有不同的状态,并且在各个状态下表现不一致的情况;这些状态之间如果是可以相互转换的,使用此设计模式就更加合适了。