浅谈设计模式——状态模式(汽车加速、升/减档)
2022/6/11 23:52:13
本文主要是介绍浅谈设计模式——状态模式(汽车加速、升/减档),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
状态模式简介
状态模式是一种常用的面向对象设计模式,多见于对象的状态会影响对象行为的场景。本篇以汽车加速、升/减档为例,介绍状态模式在实际开发中的实现思路以及注意事项。
状态模式的实现思路
-
根据状态机的5要素(状态、动作、事件、迁移、条件),画出目标对象的状态迁移图,和/或用清晰的文字准确描述5要素。以本篇为例:
- 汽车的状态即为5个档位(停止时由于没有特定动作,在状态模式下不需要特地划分成状态);
- 汽车的动作包括:加速、减速。由于减速机制与档位无关,不论速度和档位如何,都可以通过踩刹车减至停车,因此不需要将减速方法转移到表示状态的档位类;
- 外部事件在本例中没有体现,在显示中则是驾驶员踩下油门/刹车、以及控制换挡杆,如果通过代码抽象,可以用按钮表示各种操作,将这些操作作为事件进行监听
- 升档、减档就是状态的迁移
- 条件则是档位的升档上下限
相对而言,外部事件并不是那么重要。在状态模式中,外部事件并不会直接接触到状态对象,而是通过环境对象来接收事件并通知状态对象。状态对象内部一般不要添加会导致自发性迁移的事件或方法,最好由外部环境对象或其他状态系列的对象负责。
在这里,升档和减档是状态的迁移过程,而不是动作;升档和减档并不会改变“汽车行驶”这个环境对象特有的动作,只是影响行驶的速度
-
根据状态模式的规范,划分环境对象/类(此处为汽车对象/类,以下简称环境对象)和状态对象/类(此处为档位对象/类,以下简称状态对象)
-
将环境对象的动作方法中受状态影响的这部分方法的实现(具体代码)转移到状态对象中,并通过调用状态对象的同名方法来执行相应的动作;而与状态无关的动作则保留在环境对象中
-
在状态对象的动作方法中,对条件进行判定,并在验证通过后执行相应的具体行为,以及在验证失败后提示
-
对外部事件的监听则需要绑定在环境对象中,并调用环境对象的对应迁移方法
按顺序表述:
- 状态迁移过程:外部事件被监听到->触发环境对象的状态迁移方法->调用状态对象的迁移方法->内部进行条件判定并触发状态迁移或者返回提示信息
- 非状态迁移但受状态影响的动作的执行过程:外部事件被监听到->触发环境对象的动作方法->调用状态对象的动作方法->内部进行条件判定并触发动作会返回提示信息
可以看出,环境对象原本的方法可以分成三大类:
- 不受状态影响的动作方法,将实现保留在环境对象中
- 受状态影响的动作方法,将实现转移到状态对象中,避免条件分支嵌套导致的维护和扩展困难
- 状态迁移方法(不是简单的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('档位已换至四挡'); } } }
部分控制台输出截图
到达一档速度上限
档位升至三挡
档位减至四挡
对于状态模式优缺点的理解
优点部分:传统的实现思路或者是将状态迁移通过条件分支的形式嵌套起来,通过比较前后状态并选择相应的分支;或者是直接将每一种迁移封装成一个方法。前一种的扩展性很差,每次新增状态或新增迁移路径都需要修改分支或新增分支,而且结构复杂;而后一种方式则是在新增状态或新增迁移路径时,需要新增对应数量的方法,容易导致方法数暴增。这两种方法都只适合状态数和迁移路径较少的场景。
本例中的迁移路径较少,从一档到五档和从五档到一档,可以视作只有两条迁移路径,每条路径有四段变化,可能还看不出状态模式的好处。但若是五个档位间可以任意切换,同时条件也独立判定,此时仍然使用传统模式的话会导致判定结构过于复杂。而使用状态模式就能将条件判定状态迁移的执行分摊到五个状态类(也就是让状态类承担条件判定和状态迁移的职责),降低了环境对象的复杂程度,而每个状态类也只需要负责向其他四个状态类的迁移即可。
一些更为复杂的场景
- 外部事件可能导致环境对象的其他状态发生改变,进而触发当前状态系列的状态迁移,或者是限制条件或动作的效果发生改变。
比如本例中的汽车遭受撞击,导致档位失灵或者无法换挡,相当于增加了一类状态系列(正常、破损)。此时如果对原有状态对象做修改,增加限制条件,这不符合开闭原则;可能更好的扩展方式是修改环境对象,使其调用新增的正常/破损状态对象,再由该对象去调用档位状态对象。
- 环境对象具有多种相互平行的状态系列,且只允许为多个状态系列中的一种,但是不同状态系列的具体行为和判断条件可能都不同。
比如一个播放器具有停止状态(或者说准备状态)、播放状态和暂停状态,三种状态具有的动作是不同的:停止状态只有监听并迁移到播放状态;播放状态具有监听暂停和停止事件,以及处理播放源的方法;暂停状态具有监听(继续)播放和停止播放的事件,以及清除播放资源占用的方法。三种状态的可迁移方向以及拥有的动作均不相同,此时可以认为这是三个子状态系列,因此不能共用一个抽象类(或者抽象类不能包含所有方法,一般而言不需要给暂停状态添加处理播放源的方法)。或者这类场景可能需要结合其他设计模式。
这篇关于浅谈设计模式——状态模式(汽车加速、升/减档)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-05-01为什么公共事业机构会偏爱 TiDB :TiDB 数据库在某省妇幼健康管理系统的应用
- 2024-04-26敏捷开发:想要快速交付就必须舍弃产品质量?
- 2024-04-26静态代码分析的这些好处,我竟然都不知道?
- 2024-04-26你在测试金字塔的哪一层?(下)
- 2024-04-26快刀斩乱麻,DevOps让代码评审也自动起来
- 2024-04-262024年最好用的10款ER图神器!
- 2024-04-2203-为啥大模型LLM还没能完全替代你?
- 2024-04-2101-大语言模型发展
- 2024-04-17基于SpringWeb MultipartFile文件上传、下载功能
- 2024-04-14个人开发者,Spring Boot 项目如何部署