写点什么

基于状态模式: 没有实践,再多的理论都是扯淡!!!

作者:控心つcrazy
  • 2023-09-03
    广东
  • 本文字数:5232 字

    阅读完需:约 17 分钟

基于状态模式: 没有实践,再多的理论都是扯淡!!!

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

定义

  • 状态模式是一种面向对象的设计模式,它允许一个对象在其内部状态改变时改变它对应的行为。

  • 状态模式的关键在于如何区分事物内部的状态,事物内部状态的改变往往会带来事物的行为的改变。

  • 通常我们谈到封装,一般都会优先封装对象的行为(比如,某个函数),而不是对象的状态。但在状态模式中恰好相反,状态模式的关键是把事物的每种状态都封装成单独的类,跟状态有关的行为会被封装在这个类的内部

之前读耗子叔文章时,看到过有句话说没有实践的理论都是扯淡,个人很赞同。那接下来让我们用代码说话,在实际应用中实践一下吧。

例子 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(); // 输出: 无法确认,订单已完成
复制代码


  • 好了,我们可以来看下订单状态的流转过程:

  • 初始状态(pending):当订单被创建后,订单处于待处理状态。此时可进行两个操作:确认(confirm)、取消(cancel) 。确认操作后可使状态转变为已确认状态,取消操作后可使状态转变为已取消状态。

  • 已确认状态(confirm): 订单被确认后,此时可进行两种操作:取消(cancel)、发货(ship)。取消操作可使状态转变为已取消状态,发货操作可使状态转变为已发货状态。

  • 已发货状态(ship): 订单发货后,无法在进行确认(confirm)操作,因为订单已经在路上了。此时可进行两个操作:取消(cancel)、发货(ship)。取消(cancel)操作可使状态转变为已取消状态,发货操作可使订单转变为已完成状态。

  • 已完成状态(complete): 订单成功支付后,进入已完成状态。此时无法进行以下操作:确认(confirm)、取消(cancel)、发货(ship),因为订单已经完成

  • 已取消状态(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);
复制代码


  • 这段代码的状态转移过程如下:

  • 初始状态为红灯状态(RedLightState)。运行 trafficLight.display(); 会输出 "红灯亮起",并将状态设置为绿灯状态。

  • 绿灯状态(GreenLightState)是红灯状态的下一个状态。运行 trafficLight.display(); 会输出 "绿灯亮起",并将状态设置为黄灯状态。

  • 黄灯状态(YellowLightState)是绿灯状态的下一个状态。运行 trafficLight.display(); 会输出 "黄灯亮起",并将状态设置为闪烁状态。

  • 闪烁状态(BlinkingLightState)是黄灯状态的下一个状态。运行 trafficLight.display(); 会输出 "闪烁灯亮起",并开始每隔 500 毫秒切换一次灯光状态,输出灯光状态信息。

  • 在经过一定时间后,通过调用 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. 状态转移的优化:在状态的转换逻辑中可能会比较复杂,涉及多个条件的判断和状态变量的更新。可使用状态机或状态转换表来优化性能和可读性

  5. 状态对象的粒度优化:根据业务需要进行优化,若状态对象过于庞大会导致创建和切换状态开销较大,若状态对象过小,会增加状态类的数量和管理的复杂性。

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

  • 两者就像一对双胞胎,都封装了一系列的算法或行为,他们看起来一模一样,但在意图上不同。

  • 两者的相同点是:都有上下文,一些策略和状态类,上下文把请求委托给这些类来执行

  • 区别是:在状态模式中,状态和状态对应的行为是早已被封装好的,状态之间的切换早已被规定完成,改变行为发生在状态模式的内部。而在策略模式中,他们之间没有任何联系,客户必须熟知这些策略类的作用,才能随时切换算法。


发布于: 刚刚阅读数: 3
用户头像

还未添加个人签名 2021-10-17 加入

还未添加个人简介

评论

发布
暂无评论
基于状态模式: 没有实践,再多的理论都是扯淡!!!_控心つcrazy_InfoQ写作社区