实现简单有限状态机

参考原文:

UML Tutorial: Finite State Machines
Robert C. Martin
Engineering Notebook Column
C++ Report, June 98

译文(有修改):

实现简单有限状态机

 

 

1.  有限状态机

       状态机是一个有一组不同状态的集合的系统。有一个特殊状态――它描述了系统的初始状态。而其他的一个或多个状态为终止状态;当一个事件将我们带到这样的一些状态时,状态机将退出。状态是与转换相关联的,每个转换都标注有输入事件的名称。当事件发生时,我们将随着相关的转换从当前状态移动到新的状态。

       一个有限状态机包含一组状态集(states)、一个起始状态(start state)、一组输入符号集(alphabet)、一个映射输入符号和当前状态到下一状态的转换函数(transition function)的计算模型。当输入符号串,模型随即进入起始状态。它要改变到新的状态,依赖于转换函数。在有限状态机中,会有有许多变量,例如,状态机有很多与动作(actions)转换(Mealy)或状态(摩尔机)关联的动作,多重起始状态,基于没有输入符号的转换,或者指定符号和状态(非定有限状态机)的多个转换,指派给接收状态(识别者)的一个或多个状态,等等。

 

 

2.  设计有限状态机

2.1. 简单逻辑

       考虑一个地铁十字转门。这个简单的装置由一个简单的等价FSMFinite State Machine 有限状态机)控制。图1 显示了该FSM的一部分。圆角矩形框表示状态。该十字转门只有两个状态。它能被锁定,或者能被解锁。当十字转门被锁定时,人们就可以投硬币到其投币口中。这将会引发转门变换到非锁定状态。该过程由图中的箭头表示,箭头的方向指示了从锁定状态到非锁定状态。该箭头被叫做转换,因为它描述了FSM怎样从一个状态变换到另一个状态。

 

1:地铁十字转门

转换上的标注被斜杠分成了两个部分。第一个部分是触发转换的事件的名称。第二个是当转换触发时将执行动作的名称。图1 可以被解释如下:

l         假如转门处于锁定状态,当投币事件发生时,转门将转换到非锁定状态,并且解锁动作将执行。

l         假如转门处于非锁定状态,当有人通过门时,转门将转换到锁定状态,并且将执行锁定动作。

以上描述了当事物按计划发展时,转门的工作情况。假如转门开始时就处于锁定状态。当顾客想通过转门时他就必须投入硬币。该动作将引起投币事件的发生。在锁定状态下的投币事件将引发转门转换到非锁定状态,并且引起了解锁动作。接下来顾客通过该转门。在非锁定状态下的通过事件将引发转门转换到锁定状态,并引起锁定动作。

 

 

2.2. 反常逻辑

       注意还有两种可能的情况。一旦我们确定了所有的事件和状态时,就很容易将事件应用到每个状态上。见 2

2: 反常事件的转门

当转门处于锁定状态,而顾客又要想通过转门时,我们应该如何做?明显,我们应该发出某种警告。注意这种转换不会改变状态。该转门还是处于锁定状态。另一种反常状态是当转门已经处于非锁定状态时而顾客又投了硬币。在这种情况下,我们将点亮一个小的“谢谢”的灯。

       正常的事件发生于错误的时间将引发反常条件,如以上的情况。很容易忽视反常条件,而将你带入麻烦,FSM帮助我们很容易得发现它们,并指出应该怎样应对这样的情况。

 

 

2.3. 控制警告的更优方法

       当有人要强行通过转门时,保持锁定状态并也许不是最好的处理办法。我们更倾向于进入一种非正常状态。此外,我们也许可以保持该状态直到修理工明确表明该转门可以投入运行。见 3

3: 有非正常状态的转门

       注意结束非正常状态的唯一方法是通过就绪事件。该转换确定警报被解除,并且转门又被锁定。将添加一种特殊事件用来接触警报。在非正常状态下,通过事件和投币事件都被忽略。

       为连接起始状态和锁定状态的箭头添加了一个动作。该箭头是当FSM启动时的第一个转换。该动作保证转门在启动时是被锁定的。

       注意没有为锁定状态和非锁定状态添加就绪事件。该事件对那些状态没有意义,并且没有办法应对这种情况,不如发出一个运行时错误。作为默认行为,当事件被触发而没有相应转换时,FSM应该发出某种严重错误警告。

 

 

2.4. 诊断模型

       将转门配置成诊断模式(Diagnostic Mode)有利于控制该转门的各种功能及测试其探测器。图4 列出了一种可能的设计。

4: 诊断模式下的转门

       图中最大的特征是框住那些状态的大圆角矩形框。这些大圆角矩形框被叫做超状态(super state)。在转门系统中有两个超状态。分别是普通模式(Normal Mode)和诊断模式(Diagnostic Mode)。我们可以将超状态中的状态叫做子状态(substate)。

       如图所示,转门的常规操作下的状态和转换都被包含在普通模式中。除了添加一些动作用以在顾客通过转门时打开和关闭“谢谢”灯外,该模式没有任何变动。

       诊断模式需要断言诊断事件(Diagnose event)。注意该事件的转换标记从普通模式的超状态出发,而不是其中任何一个子状态。这暗示着,不管当前正处于普通模式的哪个子状态,转门都将离开该子状态并进入诊断模式超状态。我们也将引入保存设备状态行为,该行为能简单的记住“谢谢”灯,锁及警报的状态。

       超状态更像一个抽象类。抽象类只能作为派生类的一部分被实例化,而超状态只能作为子状态的一部分被访问。当进入了诊断模式的超状态时,一定是进入其中一个子状态,初始化状态图标将告诉我们将进入哪个子状态。该情况下,诊断模式由测试投币状态开始,并且关掉了“谢谢”灯。

      

2.       实现状态机

3.1. 使用嵌套switch/case语句

       有许多方法可以实现一个有限状态机。最常用的方法是嵌套的switch case语句。清单1 中的代码显示了如何实现图2所示的状态机。

 

 

清单1: 嵌套switch case语句实现有限状态机

enum State {Locked, Unlocked};

enum Event {Pass, Coin};

void Unlock();

void Lock();

void Thankyou();

void Alarm();

void Transition(Event e)

{

static State s = Locked;

switch(s)

{

case Locked:

switch(e)

{

case Coin:

s = Unlocked;

Unlock();

break;

case Pass:

Alarm();

break;

}

break;

case Unlocked:

switch(e)

{

case Coin:

Thankyou();

break;

case Pass:

s = Locked;

Lock();

break;

}

break;

}

}

 

 

       虽然这种方法很常用,但是不完善。当FMS变得日趋复杂时,嵌套的switch/case语句将变得难以阅读。会产生大段大段的相似代码。然而,FMS的逻辑和行为不可避免的被绑定在一起。在C++中有更好的方法来处理该问题。

 

 

3.2. 使用状态模式

       我们可以使用设计模式中的状态模式。图5 显示了如何将状态变换图表达为一系列类和关系。我们由一个名为Trunstile的类开始,该类只有四个成员函数:锁定,解锁,警报和谢谢信息。另有一TurnstileFSM继承于它,该类有投币,和通过事件。并且它拥有一个指向TurnstileState借口的指针。TrunsstileState有两个派生类:LockedStateUnlockStateTurnstileState拥有这两个派生类的静态实例。

       假如FSM处于锁定状态,那么TurnstileFSM的成员指针将指向LockedState的派生类TurnstileState。假如发生了投币事件,TurnstileFSMCoin成员函数将会被调用。该过程将委派到TurnstileStateCoin成员函数,而实际将会调用到LockedStateCoin函数。该方法将调用TurnstileFSM的函数SetState,其参数为指向UnlockedState静态实例的指针,而后将调用TurnstileFSM继承于Turnstile的成员函数Unlock

       这种设计有许多有点。行为与逻辑很好的分离开了。所有的逻辑都封装在TurnstileState结构中。所有的行为都被封装在Turnstile的结构中。假如想要保留行为而修改逻辑,我们将可以改变TrunstileState,或者创建Turnstile的新的派生类,使其与新状态紧密联系。另一方面,假如想要改变行为而保留逻辑,我们所需做的不过是继承与TurnstileFSM,并重载其行为。

       应此,我们保留了两者的自有度。我们能分别修改行为和逻辑。

5: 用状态模式设计的转门有限状态机

       然而,清单2中的代码并不完善。它用了大量的代码来实现一个简单的状态机。而问题就在于大量的重复代码,以下将给出更好的解决办法。

清单2: C++实现的采用状态模式的转门有限状态机

class Turnstile

{

public:

virtual void Lock();

virtual void Unlock();

virtual void Thankyou();

virutal void Lock();

};

class TurnstileFSM;

class LockedState;

class UnlockedState;

class TurnstileState

{

public:

virtual void Coin(TurnstileFSM*) = 0;

virtual void Pass(TurnstileFSM*) = 0;

protected:

static LockedState lockedState;

static UnlockedState unlockedState;

};

class TurnstileFSM : public Turnstile

{

public:

void SetState(TurnstileState* s) {itsState = s;}

void Coin() {itsState->Coin(this);}

void Pass() {itsState->Pass(this);}

+ Lock()

+ Unlock()

+ Thankyou()

+ Alarm()

Turnstile

+ Coin()

+ Pass()

+ SetState(TurnstileState)

Turnstile FSM

+ Coin(TurnstileFSM)

+ Pass(TurnstileFSM)

Turnstile State

«interface»

# locked : LockedState

# unlocked : UnlockedState

Locked State Unlocked State

private:

TurnstileState *itsState;

};

class LockedState : public TurnstileState

{

public:

virtual void Coin(TurnstileFSM* t)

{

t->SetState(&unlockedState);

t->Unlock();

}

virtual void Pass(TurnstileFSM* t)

{

t->Alarm();

}

};

class UnlockedState : public TurnstileState

{

public:

virtual void Coin(TurnstileFSM* t)

{

t->Thankyou();

}

virtual void Pass(TurnstileFSM* t)

{

t->SetState(&lockedState);

t->Lock();

}

};

LockedState TurnstileState::lockedState;

UnlockedState TurnstileState::unlockedState;

 

 

4.  总结

       有限状态机是一种用于对应用程序的逻辑控制的描述和实现的极有效的方式。可将其应用于实现通信协议,用于控制GUI中的交互,以及其他许多应用。FSMs之所以有效,在于它的很高的表达信息的密集度。大量的逻辑能用一个很小的关系图来表达。它的有效还在于它遵循简单的规则,易于验证。易于生成代码也是它有效的一面。

 

 

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值