基于状态模式: 没有实际的实践都是扯淡!!!

定义

  • 状态模式是一种面向对象的设计模式,它容许一个对象在其外部状态扭转时扭转它对应的行为。
  • 状态模式的关键在于如何辨别事物外部的状态,事物外部状态的扭转往往会带来事物的行为的扭转。
  • 通常咱们谈到封装,个别都会优先封装对象的行为(比方,某个函数),而不是对象的状态。但在状态模式中恰好相反,状态模式的要害是把事物的每种状态都封装成独自的类,跟状态无关的行为会被封装在这个类的外部
之前读耗子叔文章时,看到过有句话说没有实际的实践都是扯淡,集体很同意。那接下来让咱们用代码谈话,在理论利用中实际一下吧。

例子1:订单解决零碎

  • 在订单解决零碎中,每个订单都能够处于不同的状态(待处理,已确认,已发货,已实现, 已勾销),且在每个状态下可执行不同的操作。
// 状态接口class OrderState {    constructor(order) {        this.order = order;    }    // 定义状态办法    confirm() {        throw new Error("confirm() method must be implemented.");    }    cancel() {        throw new Error("cancel() method must be implemented.");    }    ship() {        throw new Error("ship() method must be implemented.");    }}// 具体状态类:待处理状态class PendingState extends OrderState {    confirm() {        console.log("订单已确认");        this.order.setState(new ConfirmedState(this.order));    }    cancel() {        console.log("订单已勾销");        this.order.setState(new CancelledState(this.order));    }    ship() {        console.log("无奈发货,订单未确认");    }}// 具体状态类:已确认状态class ConfirmedState extends OrderState {    confirm() {        console.log("订单已确认");    }    cancel() {        console.log("订单已勾销");        this.order.setState(new CancelledState(this.order));    }    ship() {        console.log("订单已发货");        this.order.setState(new ShippedState(this.order));    }}// 具体状态类:已发货状态class ShippedState extends OrderState {    confirm() {        console.log("无奈确认,订单已发货");    }    cancel() {        console.log("无奈勾销,订单已发货");    }    ship() {        console.log("订单已发货");    }}// 具体状态类:已实现状态class CompletedState extends OrderState {    confirm() {        console.log("无奈确认,订单已实现");    }    cancel() {        console.log("无奈勾销,订单已实现");    }    ship() {        console.log("无奈发货,订单已实现");    }}// 具体状态类:已勾销状态class CancelledState extends OrderState {    confirm() {        console.log("无奈确认,订单已勾销");    }    cancel() {        console.log("无奈勾销,订单已勾销");    }    ship() {        console.log("无奈发货,订单已勾销");    }}// 上下文类:订单class Order {    constructor() {        // 初始化状态        this.currentState = new PendingState(this);    }    // 设置以后状态    setState(state) {        this.currentState = state;    }    // 执行确认操作    confirm() {        this.currentState.confirm();    }    // 执行勾销操作    cancel() {        this.currentState.cancel();    }    // 执行发货操作    ship() {        this.currentState.ship();    }}// 示例用法const order = new Order();order.confirm(); // 输入: 订单已确认order.ship(); // 输入: 无奈发货,订单未确认order.cancel(); // 输入: 订单已勾销order.confirm(); // 输入: 订单已确认order.ship(); // 输入: 订单已发货order.confirm(); // 输入: 无奈确认,订单已发货order.cancel(); // 输入: 无奈勾销,订单已发货order.ship(); // 输入: 订单已发货order.confirm(); // 输入: 无奈确认,订单已实现
  • 好了,咱们能够来看下订单状态的流转过程:

    1. 初始状态(pending):当订单被创立后,订单处于待处理状态。此时可进行两个操作:确认(confirm)、勾销(cancel) 。确认操作后可使状态转变为已确认状态,勾销操作后可使状态转变为已勾销状态。
    2. 已确认状态(confirm): 订单被确认后,此时可进行两种操作:勾销(cancel)、发货(ship)。勾销操作可使状态转变为已勾销状态,发货操作可使状态转变为已发货状态。
    3. 已发货状态(ship): 订单发货后,无奈在进行确认(confirm)操作,因为订单曾经在路上了。此时可进行两个操作:勾销(cancel)、发货(ship)。勾销(cancel)操作可使状态转变为已勾销状态,发货操作可使订单转变为已实现状态。
    4. 已实现状态(complete): 订单胜利领取后,进入已实现状态。此时无奈进行以下操作:确认(confirm)、勾销(cancel)、发货(ship),因为订单曾经实现
    5. 已勾销状态(cancel): 订单被勾销后,进入已勾销状态,此时无奈进行以下操作:确认(confirm)、勾销(cancel)、发货(ship),因为订单曾经勾销

例子2:交通信号灯

// 信号灯状态基类class TrafficLightState {    constructor(light) {        this.light = light;    }    // 状态行为办法,子类须要实现具体逻辑    display() {}    stopBlinking() {}}// 红灯状态class RedLightState extends TrafficLightState {    display() {        console.log("红灯亮起");        this.light.setState(new GreenLightState(this.light));    }}// 绿灯状态class GreenLightState extends TrafficLightState {    display() {        console.log("绿灯亮起");        this.light.setState(new YellowLightState(this.light));    }}// 黄灯状态class YellowLightState extends TrafficLightState {    display() {        console.log("黄灯亮起");        this.light.setState(new RedLightState(this.light));    }}// 闪动状态class BlinkingLightState extends TrafficLightState {    constructor(light) {        super(light);        this.intervalId = null;    }    display() {        console.log("闪动灯亮起");        this.intervalId = setInterval(() => {            this.light.toggle();        }, 500);    }    stopBlinking() {        console.log("闪动灯进行");        clearInterval(this.intervalId);        this.light.setState(new RedLightState(this.light));    }}// 信号灯类class TrafficLight {    constructor() {        this.state = new RedLightState(this);        this.isLightOn = false;    }    setState(state) {        this.state = state;    }    display() {        this.state.display();    }    toggle() {        this.isLightOn = !this.isLightOn;        console.log(`灯光${this.isLightOn ? "亮起" : "燃烧"}`);    }    stopBlinking() {        this.state.stopBlinking();    }}// 应用示例const trafficLight = new TrafficLight();trafficLight.display(); // 红灯亮起trafficLight.display(); // 绿灯亮起trafficLight.display(); // 黄灯亮起trafficLight.setState(new BlinkingLightState(trafficLight));trafficLight.display();/**灯光亮起灯光燃烧灯光亮起灯光燃烧灯光亮起 */setTimeout(() => {    trafficLight.stopBlinking(); // 闪动灯进行,变为红灯}, 3000);
  • 这段代码的状态转移过程如下:

    1. 初始状态为红灯状态(RedLightState)。运行 trafficLight.display(); 会输入 "红灯亮起",并将状态设置为绿灯状态。
    2. 绿灯状态(GreenLightState)是红灯状态的下一个状态。运行 trafficLight.display(); 会输入 "绿灯亮起",并将状态设置为黄灯状态。
    3. 黄灯状态(YellowLightState)是绿灯状态的下一个状态。运行 trafficLight.display(); 会输入 "黄灯亮起",并将状态设置为闪动状态。
    4. 闪动状态(BlinkingLightState)是黄灯状态的下一个状态。运行 trafficLight.display(); 会输入 "闪动灯亮起",并开始每隔 500 毫秒切换一次灯光状态,输入灯光状态信息。
    5. 在通过肯定工夫后,通过调用 trafficLight.stopBlinking(); 办法,闪动状态会进行。输入 "闪动灯进行",并将状态设置为红灯状态。

实例3:音频播放器

// 状态接口class AudioPlayerState {    constructor(audioPlayer) {        this.audioPlayer = audioPlayer;    }    play() {}    pause() {}    stop() {}}// 进行状态class StopState extends AudioPlayerState {    play() {        console.log('开始播放音频');        // 切换到播放状态        this.audioPlayer.setState(this.audioPlayer.playState);    }    pause() {        console.log('音频已进行,无奈暂停');    }    stop() {        console.log('音频已进行');    }}// 播放状态class PlayState extends AudioPlayerState {    play() {        console.log('音频曾经在播放中');    }    pause() {        console.log('音频已暂停');        // 切换到暂停状态        this.audioPlayer.setState(this.audioPlayer.pauseState);    }    stop() {        console.log('音频已进行');        // 切换到进行状态        this.audioPlayer.setState(this.audioPlayer.stopState);    }}// 暂停状态class PauseState extends AudioPlayerState {    play() {        console.log('音频曾经在播放中');    }    pause() {        console.log('音频曾经暂停');    }    stop() {        console.log('音频已进行');        // 切换到进行状态        this.audioPlayer.setState(this.audioPlayer.stopState);    }}// 音频播放器类class AudioPlayer {    constructor() {        // 初始化默认状态为进行状态        this.stopState = new StopState(this);        this.playState = new PlayState(this);        this.pauseState = new PauseState(this);        this.currentState = this.stopState;    }    setState(state) {        this.currentState = state;    }    play() {        this.currentState.play();    }    pause() {        this.currentState.pause();    }    stop() {        this.currentState.stop();    }}// 示例用法const audioPlayer = new AudioPlayer();audioPlayer.play(); // 开始播放音频audioPlayer.pause(); // 音频已暂停audioPlayer.play(); // 音频曾经在播放中audioPlayer.stop(); // 音频已进行audioPlayer.stop(); // 音频已进行
  • 大家可依据下面的例子可自行推理一下这段代码的状态转移过程。

    状态模式的优缺点

  • 长处:

    1. 封装状态的变动:将每个状态封装成一个独立的类,使得状态转移的逻辑被封装在状态类中。这使得状态变动的逻辑与主体类拆散,进步了代码的可维护性和可扩展性
    2. 简化条件语句:通过将状态判断和状态行为拆散,防止了大量的条件语句。
    3. 合乎凋谢——关闭准则:当增加新的状态时,不须要扭转原有代码。
    4. 进步了代码的可扩大
  • 毛病:

    1. 减少了类的数量:引入状态模式会减少零碎中的类的数量,每个状态都须要一个独立的类来示意,这会导致类的数量过多,减少了零碎的复杂性。
    2. 状态转移逻辑简单
    3. 不适宜状态过多的状况

状态模式的性能优化点

  1. 惰性初始化:提早初始化对象可缩小启动时的开销,可将状态对象的创立提早到真正须要的时候再进行初始化,而不是在启动时创立所有可能的状态对象
  2. 缓存状态对象:频繁的创立和销毁会重大影响性能,可应用对象池或缓存来治理状态对象的创立和复用
  3. 状态判断的优化:如果状态判断的逻辑简单,可思考应用策略模式来优化状态判断的性能
  4. 状态转移的优化:在状态的转换逻辑中可能会比较复杂,波及多个条件的判断和状态变量的更新。可应用状态机或状态转换表来优化性能和可读性
  5. 状态对象的粒度优化:依据业务须要进行优化,若状态对象过于宏大会导致创立和切换状态开销较大,若状态对象过小,会减少状态类的数量和治理的复杂性。

状态模式和策略模式的关系

  • 两者就像一对双胞胎,都封装了一系列的算法或行为,他们看起来截然不同,但在用意上不同。
  • 两者的相同点是:都有上下文,一些策略和状态类,上下文把申请委托给这些类来执行
  • 区别是:在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换早已被规定实现,扭转行为产生在状态模式的外部。而在策略模式中,他们之间没有任何分割,客户必须熟知这些策略类的作用,能力随时切换算法。