用ts学习状态模式
1. 什么是状态机
?
做产品的时候,我们总能遇到一些比较复杂的逻辑问题,而普通的流程图,或时序图对于对象和状态的解读缺乏直观的描述。
有限状态机,(英语:Finite-state machine, FSM),又称有限状态自动机,简称状态机,是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型。
2. 状态机图
怎么画?
做需求时,需要了解以下六种元素:起始、终止、现态、次态(目标状态)、动作、条件,我们就可以完成一个状态机图了:
①现态:是指当前所处的状态。
②条件:又称为“事件”,当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移。
③动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
④次态:条件满足后要迁往的新状态。“次态”是相对于“现态”而言的,“次态”一旦被激活,就转变成新的“现态”了。
注意事项
1、避免把某个“程序动作”当作是一种“状态”来处理,那么如何区分“动作”和“状态”?“动作”是不稳定的,即使没有条件的触发,“动作”一旦执行完毕就结束了;而“状态”是相对稳定的,如果没有外部条件的触发,一个状态会一直持续下去。
2、状态划分时漏掉一些状态,导致跳转逻辑不完整。所以在设计状态机时,我们需要反复的查看设计的状态图或者状态表,最终达到一种牢不可破的设计方案。
3. 什么是状态模式
?
状态模式(state pattern)
定义:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。
4. 如何用ts来实现状态模式
?
万能糖果公司想设计一款糖果自动售货机,默认发放一颗糖果,实现转动曲柄有概率发放两颗糖果的整个功能
状态图如下:
首先分析状态图,分别有
-
四个状态,即
- 没有 25分钱
- 有 25分钱
- 售出糖果
- 糖果售罄
-
四个行为动作,即
- 投入25分钱
- 退回25分钱
- 转动曲柄
- 发放糖果
设计:
1. 我们将 所有的行为动作封装在接口内,每个动作都对应一个方法
2. 每个状态实现接口状态类,这些类将负责在对应的状态下进行机器的行为。
3. 将动作委托都状态类。
好了分析了设计方法,代码如下
接口
interface State {
insertQuarter:void,
ejectQuarter:void,
turnQuarter:void,
dispense():void
}
机器
class machine {
private noQuarterState:State,
private hasQuarterState:State,
private soldState:State,
private soldOut:State,
private winnerState:State,
private state:State,
private count:number
constructor(count:number){
this.count = count;
this.noQuarterState = new NoQuarterState(this)
this.hasQuarterState = new HasQuarterState(this)
this.soldState = new SoldState(this)
this.soldOut = new SoldOut(this)
this.winnerState = new WinnerState()
if(count > 0)
{
this.setState(this.getNoQuarterState())
}else{
this.setState(this.getSoldOutState())
}
}
public setState(state:state){
this.state = state
}
public getState(){
return this.state
}
public getNoQuarterState(){
return this.noQuarterState
}
public getHasQuarterState(){
return this.hasQuarterState
}
public getSoldState(){
return this.soldState
}
public getSoldOutState(){
return this.soldOut
}
public getWinnerState(){
return this.winnerState
}
public getCount(){
return this.count
}
public releaseBall(){
comsole.log("发放糖果")
}
public insertQuarter(){ //将投币脱硝给当前状态
this.state.insertQuarter()
}
public ejectQuarter(){ //将退钱脱销给当前状态
this.state.ejectQuarter()
}
public turnCrank(){ //将售糖果脱销给当前状态
this.state.turnCrank()
}
public dispense(){ //将发放糖果脱销给当前状态
this.state.dispense()
}
}
每个状态
class NoQuarterState implements State { // 没有 25 分钱
private gumballMachine:GumballMachine
public name: string
constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
this.gumballMachine = gumballMachine
this.name = '没有 25 分钱'
}
insertQuarter() {
this.gumballMachine.setState(this.gumballMachine.getHasQuarterState())
}
ejectQuarter() { // 如果没给钱就不能要求退钱
console.log('你没有投币。')
}
turnCrank(){ // 如果没给钱就不能要求售出糖果
console.log('你摇动了曲柄,但是没有投币。')
}
dispense(){ // 如果没给钱就不能发放糖果
console.log('你需要先投币')
}
}
class HasQuarterState implements State { // 有 25 分钱
private gumballMachine:GumballMachine
public name: string
constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
this.gumballMachine = gumballMachine
this.name = '有 25 分钱'
}
insertQuarter() {
console.log('你已经投过币了,不需要再投币了。')
}
ejectQuarter() {
console.log('返还 25 分钱')
this.gumballMachine.setState(this.gumballMachine.getNoQuarterState())
}
turnCrank(){
console.log('售出糖果')
let winner = Math.random() <= 0.1
if (winner) {
this.gumballMachine.setState(this.gumballMachine.getWinnerState())
} else {
this.gumballMachine.setState(this.gumballMachine.getSoldState())
}
}
dispense(){ // 这是一个不应该发生的动作
console.log('还未摇动摇杆,无法售出糖果。')
}
}
class SoldState implements State {
private gumballMachine:GumballMachine
public name: string
constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
this.gumballMachine = gumballMachine
this.name = '售出糖果'
}
insertQuarter() { // 在售出糖果的时候不能再投币了
console.log('已经投过币了,请等待糖果售出。')
}
ejectQuarter() {
console.log('对不起,你已经摇动了售货曲柄,无法退钱了...')
}
turnCrank(){
console.log('你已经摇动过售货曲柄了,同一次购买不能售货两次。')
}
dispense(){ // 发放糖果
this.gumballMachine.releaseBall() // 发放糖果
// 改变至下一个状态
if (this.gumballMachine.getCount() > 0) { // 库存为正
this.gumballMachine.setState(this.gumballMachine.getNoQuarterState()) // 设置到未投币状态
} else {
this.gumballMachine.setState(this.gumballMachine.getSoldOutState()) // 库存等于小于零 设置到售罄状态
}
}
}
class SoldOutState implements State {
private gumballMachine:GumballMachine
public name: string
constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
this.gumballMachine = gumballMachine
this.name = '售罄'
}
insertQuarter() {
console.log('糖果已售罄不能投币。')
}
ejectQuarter() {
console.log('对不起,糖果已售罄你无法投币,也无法退币。')
}
turnCrank(){
console.log('你已经摇了曲柄,但是糖果已售罄。')
}
dispense(){
console.log('糖果已售罄,无法发放糖果.')
}
}
赢家
class WinnerState implements State {
private gumballMachine:GumballMachine
public name: string
constructor(gumballMachine:GumballMachine){ // 初始化的时候获取糖果机
this.gumballMachine = gumballMachine
this.name = '赢家'
}
insertQuarter() { // 在售出糖果的时候不能再投币了
console.log('已经投过币了,请等待糖果售出。')
}
ejectQuarter() {
console.log('对不起,你已经摇动了售货曲柄,无法退钱了...')
}
turnCrank(){
console.log('你已经摇动过售货曲柄了,同一次购买不能售货两次。')
}
dispense(){
console.log('恭喜你,你获得了赢家状态,如果库存充足的话,你讲获得额外的一颗糖!')
this.gumballMachine.releaseBall() // 发放第一颗糖果
// 改变至下一个状态
if (this.gumballMachine.getCount() > 0) { // 库存为正
this.gumballMachine.releaseBall() // 发放第二课糖果
if (this.gumballMachine.getCount() > 0) {
this.gumballMachine.setState(this.gumballMachine.getNoQuarterState()) // 设置到未投币状态
} else {
this.gumballMachine.setState(this.gumballMachine.getSoldOutState()) // 库存等于小于零 设置到售罄状态
}
} else {
this.gumballMachine.setState(this.gumballMachine.getSoldOutState()) // 库存等于小于零 设置到售罄状态
}
}
}
测试
// 实例化糖果售货机
let gumballMachine = new GumballMachine(10) // 放入 10 颗糖果
function test(){
// 投入一枚25分钱的硬币
console.log('投入一枚25分钱的硬币')
gumballMachine.insertQuarter()
// 转动曲柄
console.log('转动曲柄')
gumballMachine.turnCrank()
console.log(`当前库存${gumballMachine.getCount()}`)
}
test()
let interval: number
interval = setInterval(()=>{
if (gumballMachine.getCurrentState().name === '售罄') {
console.log('售罄')
clearInterval(interval)
} else {
test()
}
}, 5000);
浏览器输出结果:
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:183 发放糖果
VM153:334 当前库存4
1
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:183 发放糖果
VM153:334 当前库存3
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:307 恭喜你,你获得了赢家状态,如果库存充足的话,你讲获得额外的一颗糖!
VM153:183 发放糖果
VM153:183 发放糖果
VM153:334 当前库存1
VM153:329 投入一枚25分钱的硬币
VM153:332 转动曲柄
VM153:233 售出糖果
VM153:307 恭喜你,你获得了赢家状态,如果库存充足的话,你讲获得额外的一颗糖!
VM153:183 发放糖果
VM153:334 当前库存0
VM153:340 售罄