Skip to content

状态变化模式

概念

定义

  • 在组件构建过程中,某些对象的状态经常面临变化,如何对这些变化进行有效的管理?
  • 同时又维持高层模块的稳定?
  • “状态变化”模式为这一问题提供了一种解决方案。

状态模式(State)

动机

  • 在软件构建过程中,某些对象的状态如果改变,其行为也会随之而变化,比如文档处于只读状态,其支持的行为和读写状态支持的行为就可能完全不同。
  • 如何在运行时根据对象的状态来透明地更改对象的行为?而不会为对象操作和状态转化之间引入紧耦合?

定义

  • 每种状态独立封装,不同状态内部封装了不同行为。
  • 允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。

UML 类图

x

角色

  • 环境类(Context):定义了客户端需要的接口,内部维护一个当前状态,并负责具体状态的切换。
  • 抽象状态类(State):定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
  • 具体状态类(ConcreteState):实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。

代码

// 抽象状态类
abstract class State {
  public abstract Handle(context: Context): void;
}

// 具体状态A类
class ConcreteStateA extends State {
  public Handle(context: Context) {
    console.log('当前状态是 A.');
    context.setState(new ConcreteStateB());
  }
}

// 具体状态B类
class ConcreteStateB extends State {
  public Handle(context: Context) {
    console.log('当前状态是 B.');
    context.setState(new ConcreteStateA());
  }
}

// 环境类
class Context {
  private state: State;

  // 定义环境类的初始状态
  constructor() {
    this.state = new ConcreteStateA();
  }

  // 设置新状态
  public setState(state: State) {
    this.state = state;
  }

  // 读取状态
  public getState(): State {
    return this.state;
  }

  // 对请求做处理
  public Handle() {
    this.state.Handle(this);
  }
}

class PatternClient {
  public static main(args) {
    // 创建环境
    const context: Context = new Context();

    // 处理请求
    context.Handle();
    context.Handle();
    context.Handle();
    context.Handle();
  }
}

export default PatternClient;

要点总结

  • State模式将所有与一个特定状态相关的行为都放入一个State的子类对象中,在对象状态切换时,切换相应的对象;但同时维持State的接口,这样实现了具体操作与状态转换之间的解耦。
  • 为不同的状态引入不同的对象,使得状态转化变得更加明确,而且可以保证不会出现状态不一致的情况,因为转换时原子性的——即要么彻底转换过来,要么不转换。
  • 如果State对象没有实例变量,那么各个上下文可以共享同一个State对象,从而节省对象开销。

备忘录模式(Memento)

动机

  • 在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象之前处于某个点时的状态。如果使用一些共有的接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
  • 如何实现对象状态的良好保存与恢复?同时又不会因此而破坏对象的本身的封装性?

模式定义

  • 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
  • 又叫快照模式。
  • 除了创建它的发起人(originator)之外,其他对象都不能够访问这些状态信息。

UML 类图

x

角色

  • 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
  • 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
  • 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。

代码

// 备忘录
class Memento {
  private state: string;

  constructor(state: string) {
    this.state = state;
  }

  public setState(state: string): void {
    this.state = state;
  }

  public getState(): string {
    return this.state;
  }
}

// 发起人
class Originator {
  private state: string;

  public setState(state: string): void {
    this.state = state;
  }

  public getState(): string {
    return this.state;
  }

  public createMemento(): Memento {
    return new Memento(this.state);
  }

  public restoreMemento(m: Memento): void {
    this.setState(m.getState());
  }
}

// 管理者
class Caretaker {
  private memento: Memento;

  public setMemento(m: Memento): void {
    this.memento = m;
  }

  public getMemento(): Memento {
    return this.memento;
  }
}

class PatternClient {
  public static main(args: Array<string>): void {
    const or: Originator = new Originator();
    const cr: Caretaker = new Caretaker();

    or.setState('S0');
    console.log('初始状态:' + or.getState());

    cr.setMemento(or.createMemento()); // 保存状态

    or.setState('S1');
    console.log('新的状态:' + or.getState());

    or.restoreMemento(cr.getMemento()); // 恢复状态
    console.log('恢复状态:' + or.getState());
  }
}

export default PatternClient;

要点总结

  • 备忘录(Memento)存储原发器(Originator)对象的内部状态,在需要时恢复原发器状态。
  • Memento模式的核心是信息隐藏,即Originator需要向外界隐藏信息,保持其封装性。同时又需要将状态保持到外界(memento)。
  • 由于语言运行时(c#,java等)都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。