浅谈设计模式——状态模式(汽车加速、升/减档)

2022/6/11 23:52:13

本文主要是介绍浅谈设计模式——状态模式(汽车加速、升/减档),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

状态模式简介

状态模式是一种常用的面向对象设计模式,多见于对象的状态会影响对象行为的场景。本篇以汽车加速、升/减档为例,介绍状态模式在实际开发中的实现思路以及注意事项。

状态模式的实现思路

  1. 根据状态机的5要素(状态、动作、事件、迁移、条件),画出目标对象的状态迁移图,和/或用清晰的文字准确描述5要素。以本篇为例:

    1. 汽车的状态即为5个档位(停止时由于没有特定动作,在状态模式下不需要特地划分成状态);
    2. 汽车的动作包括:加速、减速。由于减速机制与档位无关,不论速度和档位如何,都可以通过踩刹车减至停车,因此不需要将减速方法转移到表示状态的档位类;
    3. 外部事件在本例中没有体现,在显示中则是驾驶员踩下油门/刹车、以及控制换挡杆,如果通过代码抽象,可以用按钮表示各种操作,将这些操作作为事件进行监听
    4. 升档、减档就是状态的迁移
    5. 条件则是档位的升档上下限

    相对而言,外部事件并不是那么重要。在状态模式中,外部事件并不会直接接触到状态对象,而是通过环境对象来接收事件并通知状态对象。状态对象内部一般不要添加会导致自发性迁移的事件或方法,最好由外部环境对象或其他状态系列的对象负责。

    在这里,升档和减档是状态的迁移过程,而不是动作;升档和减档并不会改变“汽车行驶”这个环境对象特有的动作,只是影响行驶的速度

  2. 根据状态模式的规范,划分环境对象/类(此处为汽车对象/类,以下简称环境对象)和状态对象/类(此处为档位对象/类,以下简称状态对象)

  3. 将环境对象的动作方法中受状态影响的这部分方法的实现(具体代码)转移到状态对象中,并通过调用状态对象的同名方法来执行相应的动作;而与状态无关的动作则保留在环境对象中

  4. 在状态对象的动作方法中,对条件进行判定,并在验证通过后执行相应的具体行为,以及在验证失败后提示

  5. 对外部事件的监听则需要绑定在环境对象中,并调用环境对象的对应迁移方法

按顺序表述:

  1. 状态迁移过程:外部事件被监听到->触发环境对象的状态迁移方法->调用状态对象的迁移方法->内部进行条件判定并触发状态迁移或者返回提示信息
  2. 非状态迁移但受状态影响的动作的执行过程:外部事件被监听到->触发环境对象的动作方法->调用状态对象的动作方法->内部进行条件判定并触发动作会返回提示信息

可以看出,环境对象原本的方法可以分成三大类:

  1. 不受状态影响的动作方法,将实现保留在环境对象中
  2. 受状态影响的动作方法,将实现转移到状态对象中,避免条件分支嵌套导致的维护和扩展困难
  3. 状态迁移方法(不是简单的set方法,而是需要在设置前,对限制条件进行判定,相当于拦截设置操作),同第2点,将实现转移

示例

本例以typescript编写,仅使用一个模块Car.ts和一个主模块index.ts。

index.ts

import { Car } from './scripts/modules/Car';

let car: Car = new Car();

// 类的设计中没有持续加/减速的功能,这里简单替代一下
// time表示加速持续时间,单位是秒,每秒汽车会加速1km/h
function faster(time: number) {
    for (let index = 0; index < time; index++) {
        car.faster();
        console.log('当前速度是:' + car.speed);
    }
}
function slower(time: number) {
    for (let index = 0; index < time; index++) {
        car.slower();
        console.log('当前速度是:' + car.speed);
    }
}
 
// 加速过程
faster(20); // 一档速度上限是15km/h,因此加速20秒只能到15,之后继续踩油只会报警提示
car.upGear(); // 升二档
faster(20);
car.upGear(); // 升三档
faster(20);
car.upGear(); // 升四档
faster(20);
car.upGear(); // 升五档
faster(20); // 五档上限为120km/h,因此这里不会报警提示

// 减速过程
slower(25);
car.downGear(); // 减到四挡
slower(60); // 一直减速至停车,不换挡

Car.ts

export class Car {
    private gear: CarGear = new FirGear(this);
    speed: number = 0;

    setGear(gear: CarGear) {
        this.gear = gear;
    }

    // 减速
    // 由于减速机制与档位无关,不论速度和档位如何,都可以通过踩刹车减至停车,
 // 因此不需要将减速方法转移到表示状态的档位类
    slower(): void {
        if (this.speed > 0) {
            this.speed -= 1;
        } else {
            console.warn('汽车已停止');
        }
    }
 
 // 加速、升档、减档三个方法都受到档位的限制:
    // 加速到档位上限后,需要升档才能继续加速,否则会报警提示
    // 升档和减档则是分别要求速度达到档位的下限和上限,才允许升档和减档操作成功,否则会报警提示

    // 加速
    faster() {
        this.gear.faster();
    }
    // 升档
    upGear() {
        this.gear.upGear();
    }
    // 减档
    downGear() {
        this.gear.downGear();
    }
}
// 表示状态的档位类
class CarGear {
    protected gearName: string = '';
    constructor(protected car: Car,
        readonly lowerLimit: number = 0,
        readonly upperLimit: number = 0) { }
 
    // 加速
    faster(): void {
        if (this.car.speed < this.upperLimit) {
            this.car.speed += 1;
        } else {
            console.error(`汽车已加速至${this.gearName}上限,请升档`);
        }
    }
    // 升档
    upGear(): void { }
    // 减档
    downGear(): void { }
}
//  一档
class FirGear extends CarGear {
    constructor(protected car: Car) {
        super(car, 0, 15);
        this.gearName = '一档'
    }
    // 加速
    faster() {
        if (this.car.speed === 0) {
            console.warn('汽车启动');
        }
        super.faster();
    }
    // 升档
    upGear() {
        // super.checkUpGear(new SndGear(this.car));
        // super.upGear();
        let sndGear = new SndGear(this.car);
        if (this.car.speed >= sndGear.lowerLimit) {
            this.car.setGear(sndGear);
            console.info('档位已换至二挡');
        }
    }
}
// 二挡
class SndGear extends CarGear {
    constructor(protected car: Car) {
        super(car, 10, 25)
    }
    // 加速
    faster() {
        super.faster();
    }
    // 升档
    upGear() {
        // super.upGear(new TrdGear(this.car));
        let trdGear = new TrdGear(this.car);
        if (this.car.speed >= trdGear.lowerLimit) {
            this.car.setGear(trdGear);
            console.log('档位已换至三挡');
        }
    }
    // 减档
    downGear() {
        // super.upGear(new FirGear(this.car));
        let firGear = new FirGear(this.car);
        if (this.car.speed <= firGear.upperLimit) {
            this.car.setGear(firGear);
            console.log('档位已换至一挡');
        }
    }
}
// 三挡
class TrdGear extends CarGear {
    constructor(protected car: Car) {
        super(car, 20, 45)
    }
    // 加速
    faster() {
        super.faster();
    }
    // 升档
    upGear() {
        // super.upGear(new FourthGear(this.car));
        let fourthGear = new FourthGear(this.car);
        if (this.car.speed >= fourthGear.lowerLimit) {
            this.car.setGear(fourthGear);
            console.log('档位已换至四挡');
        }
    }
    // 减档
    downGear() {
        // super.upGear(new SndGear(this.car));
        let sndGear = new SndGear(this.car);
        if (this.car.speed <= sndGear.upperLimit) {
            this.car.setGear(sndGear);
            console.log('档位已换至二挡');
        }
    }
}
// 四挡
class FourthGear extends CarGear {
    constructor(protected car: Car) {
        super(car, 40, 60)
    }
    // 加速
    faster() {
        super.faster();
    }
    // 升档
    upGear() {
        // super.upGear(new FifthGear(this.car));
        let fifthGear = new FifthGear(this.car);
        if (this.car.speed >= fifthGear.lowerLimit) {
            this.car.setGear(fifthGear);
            console.log('档位已换至五挡');
        }
    }
    // 减档
    downGear() {
        // super.upGear(new TrdGear(this.car));
        let trdGear = new TrdGear(this.car);
        if (this.car.speed <= trdGear.upperLimit) {
            this.car.setGear(trdGear);
            console.log('档位已换至三挡');
        }
    }
}
// 五挡
class FifthGear extends CarGear {
    constructor(protected car: Car) {
        super(car, 60, 120)
    }
    // 加速
    faster() {
        super.faster();
    }
    // 减档
    downGear() {
        // super.upGear(new FourthGear(this.car));
        let fourthGear = new FourthGear(this.car);
        if (this.car.speed <= fourthGear.upperLimit) {
            this.car.setGear(fourthGear);
            console.log('档位已换至四挡');
        }
    }
}

部分控制台输出截图

到达一档速度上限

档位升至三挡

档位减至四挡

对于状态模式优缺点的理解

优点部分:传统的实现思路或者是将状态迁移通过条件分支的形式嵌套起来,通过比较前后状态并选择相应的分支;或者是直接将每一种迁移封装成一个方法。前一种的扩展性很差,每次新增状态或新增迁移路径都需要修改分支或新增分支,而且结构复杂;而后一种方式则是在新增状态或新增迁移路径时,需要新增对应数量的方法,容易导致方法数暴增。这两种方法都只适合状态数和迁移路径较少的场景。
本例中的迁移路径较少,从一档到五档和从五档到一档,可以视作只有两条迁移路径,每条路径有四段变化,可能还看不出状态模式的好处。但若是五个档位间可以任意切换,同时条件也独立判定,此时仍然使用传统模式的话会导致判定结构过于复杂。而使用状态模式就能将条件判定状态迁移的执行分摊到五个状态类(也就是让状态类承担条件判定和状态迁移的职责),降低了环境对象的复杂程度,而每个状态类也只需要负责向其他四个状态类的迁移即可。

一些更为复杂的场景

  1. 外部事件可能导致环境对象的其他状态发生改变,进而触发当前状态系列的状态迁移,或者是限制条件或动作的效果发生改变。

    比如本例中的汽车遭受撞击,导致档位失灵或者无法换挡,相当于增加了一类状态系列(正常、破损)。此时如果对原有状态对象做修改,增加限制条件,这不符合开闭原则;可能更好的扩展方式是修改环境对象,使其调用新增的正常/破损状态对象,再由该对象去调用档位状态对象。

  2. 环境对象具有多种相互平行的状态系列,且只允许为多个状态系列中的一种,但是不同状态系列的具体行为和判断条件可能都不同。

    比如一个播放器具有停止状态(或者说准备状态)、播放状态和暂停状态,三种状态具有的动作是不同的:停止状态只有监听并迁移到播放状态;播放状态具有监听暂停和停止事件,以及处理播放源的方法;暂停状态具有监听(继续)播放和停止播放的事件,以及清除播放资源占用的方法。三种状态的可迁移方向以及拥有的动作均不相同,此时可以认为这是三个子状态系列,因此不能共用一个抽象类(或者抽象类不能包含所有方法,一般而言不需要给暂停状态添加处理播放源的方法)。或者这类场景可能需要结合其他设计模式。



这篇关于浅谈设计模式——状态模式(汽车加速、升/减档)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程