0

假设我们有一个(英国)交通灯模拟应用程序,并且一个类TrafficLight有一个相关的有限状态机,定义为:-

*  -->  RED  -->  RED_AMBER  -->  GREEN  -->  AMBER  -->  RED  --> ...
(repeat until the proverbial cows make an appearance )

在构建时 TrafficLight的状态是 RED 某种时间触发器会导致状态变化。

在应用程序中可能有一些代码,例如(删除任何带走点的代码)......

TrafficLight trafficLightsAtBigJunction = new TrafficLight();  // state = RED
trafficLightsAtBigJunction.setState( TrafficLightState.RED_AMBER );
trafficLightsAtBigJunction.setState( TrafficLightState.GREEN );
trafficLightsAtBigJunction.setState( TrafficLightState.AMBER );
trafficLightsAtBigJunction.setState( TrafficLightState.RED );
trafficLightsAtBigJunction.setState( TrafficLightState.RED_AMBER );
:
:
:

关键是,使用状态模式来实现状态机,如果我们这样做

TrafficLight trafficLightsAtBigJunction = new TrafficLight();  // state = RED
trafficLightsAtBigJunction.setState( TrafficLightState.GREEN ); // Exception!!!!!

抛出异常(根据我们的设计),因为它是非法的状态移动。这就是我们想要的。世界上的一切都很好。

但是,如果我们继续保持红绿灯并且它恰好处于状态 = AMBER 说那么似乎有问题。当我们的用户在 3 天后回来观看令人敬畏的交通灯模拟时,它会从某些(谁在乎)持久存储中的当前状态恢复。

我们如何在不破坏此处状态模式提供的封装的情况下使交通灯实例处于状态 AMBER?

似乎有 2 个选择:- (1) 创建一个新实例并运行相关状态 (2) 提供一种特殊方法来将状态设置为我们想要的任何状态,按照惯例,只有在从一些持久性存储中读取后才会使用. 例如

trafficLight.setStateAfterReadingFromPersistanceSource( AMBER );

我看到的(1)的问题是,在运行状态时很可能会出现我不想要的副作用,而且逻辑可能非常复杂,具体取决于状态机

(2) 的问题显然是它只能按约定工作,因此可能会在不知道何时不正确使用的情况下引入错误。更重要的是,它几乎打破了所有你想要的漂亮模式封装。

问题与持久性技术无关 - ORM、文件、序列化等问题相同

我假设这里有一个解决方案,但我自己想不出一个解决方案,而且我的谷歌搜索技能还不够。

任何指针都会很棒。

4

4 回答 4

2

通过将状态和转换表示为对象来实现状态机当然是可能的,但是这些对象需要初始化(这似乎是您的问题)并占用宝贵的 RAM。

但是,还有一种完全不同的方式将状态机实现为纯代码。这有很多优点,我永远不会回到“状态机作为数据”的方法。

举一个具体的例子, http://www.drdobbs.com/architecture-and-design/uml-statecharts-at-1099/188101799上的 DDJ 文章“UML Statecharts at $10.99”准确地展示了如何实现 Pedestrian LIght CONtrolled ( PELICAN) 交叉作为分层状态机。

此示例用 C 语言编写,用于低端微控制器。如果您对 C++ 实现感兴趣,可以查看 SourceForge.net 上的开源 QP/C++ 框架,网址为https://sourceforge.net/projects/qpc/files/QP_C%2B%2B/

于 2013-02-09T15:09:52.317 回答
1

在我看来,您需要两种方式来操纵状态:

1)从这个状态转换到另一个状态,执行这个转换的所有副作用,如果非法则抛出异常等

2) 将机器直接设置为一个状态/一组内部值。什么都不做。

你应该坚持所有描述 FSM 内部状态的东西,并有两种方法,一种是前者,一种是后者。

后者将在设置或取消持久化时使用。编码也更简单,因为它只是将值转换为变量,而不用担心还需要发生什么。

前者将在模拟过程中使用。

于 2013-02-06T21:43:12.363 回答
1

对于简短的回答,我同意皮特的观点,在这个简单的示例中,您可以将其作为构造函数 arg 传递。

但老实说,我认为整个设计是有缺陷的。我认为这应该使用标准的状态设计模式来建模。像这样的东西:

class TrafficLight 
{
   private TrafficLightState _lightState;

   TrafficLight(initialState)
   {
      // utilize lookup table or factory-method to assign _lightState with  the correct TrafficLightState subclass
   }

   // UI can use this to identify/render the appropriate color
   Color getColorCode()
   {
      return _lightState.getColorCode();
   }

   // UI uses this to know when to signal the next light change (each color can have different duration)
   int getDuration()
   {
      return _lightState.getDuration();
   }

   // assuming the UI has a timer that is set based on the current light's duration
   void changeLight()
   {
      TrafficLightState nextState = _lightState.onChangeLight();
      _lightState = nextState;
   }

}

abstract class TrafficLightState
{
   abstract Color getColorCode()
   abstract TrafficLightState onChangeLight()
   abstract int getDuration()
}


class RedLight : TrafficLightState
{

   Color getColorCode()
   {
      return Color.Red;
   }

   TrafficLightState  onChangeLight()
   {
      return new RedAmberLight();
   }

   int getDuration() 
   {
      return 30;
   }
}

class RedAmberLight : TrafficLightState
{

   Color getColorCode()
   {
      return Color.Orange;
   }

   TrafficLightState  onChangeLight()
   {
      return new GreenLight();
   }

   int getDuration() 
   {
      return 10;
   }
}

class GreenLight: TrafficLightState
{

   Color getColorCode()
   {
      return Color.Green;
   }

   TrafficLightState  onChangeLight()
   {
      return new AmberLight();
   }

   int getDuration() 
   {
      return 25;
   }
}

class AmberLight: TrafficLightState
{

   Color getColorCode()
   {
      return Color.Yellow;
   }

   TrafficLightState  onChangeLight()
   {
      return new RedLight();
   }

   int getDuration() 
   {
      return 10;
   }
}

状态机不应具有用于在正常操作中转换的明确公开的“更改状态”方法。相反,将它们视为具有允许状态机转换其自身状态的刺激。在此示例中,刺激非常简单,但通常您会有一组可能导致状态转换的输入。但是通过适当的封装,调用者不需要过分了解细节。

于 2013-02-07T15:35:33.320 回答
1

最简单的方法可能只是将初始状态作为构造函数参数传递 - 这只是您的约定,系统以所有灯为红色开始。

另一种方法是使从商店中提取数据的功能成为朋友或会员(取决于您是使用 operator>> 来阅读它还是其他东西)。这使您可以选择根据您的示例转换到状态,或从商店读取初始状态。对于正在发生的事情并没有太多的歧义,并且在持久化时由 FSM 拉取其状态以及它需要进出存储的任何其他内容。

于 2013-02-06T22:39:48.730 回答