使用状态模式实现层次状态机的最佳实践是什么?

9
我即将使用状态模式在C#中实现分层状态机。我正在使用这个示例作为指南。不过,该示例并没有提供关于分层状态的答案。不幸的是,我似乎找不到其他好的示例。我的第一个想法是为分层状态创建嵌套类。但是,这是否被认为是最佳实践还是有更好的解决方案呢?
问候!
更新:
我一下午都在尝试按照上述描述实现状态模式。HSM基于一个非常简单的媒体播放器: alt text http://www.freeimagehosting.net/uploads/e8d2d6486a.jpg 我以为我已经做到了,但有一件事我不明白。首先,是我写的代码(抱歉,它相当多):
public class MediaPlayer
{
    public MediaPlayerStates state;

    public MediaPlayer(MediaPlayerStates state)
    {
        this.state = state;
    }

    public void OnButtonPressed()
    {
        state.OnButtonPressed(this);
    }

    public void DeviceBooted()
    { 
        state. ?????
    }

    //Other Functions
}

//The 3 initial states (Start, On, End) know only 2 events.
public abstract class MediaPlayerStates
{
    public abstract void OnButtonPressed(MediaPlayer player);
    public abstract void OffButtonPressed(MediaPlayer player);
}

//The very beginpoint of the state machine
public class Start : MediaPlayerStates
{
    //When hitting the onbutton, the state changes to the OnState state
    public override void OnButtonPressed(MediaPlayer player)
    {
        player.state = new OnState(player);
    }

    //No need to implement this one
    public override void OffButtonPressed(MediaPlayer player)
    {
        throw new NotImplementedException();
    }
}

//OnState implements the 2 events from the MediaPlayerStates abstract class.
public class OnState : MediaPlayerStates
{
    //When entered the OnState state, a new entrypoint is creaeted: the Start state
    public OnState(MediaPlayer player)
    {
        player.state = new OnStartState();
    }

    //The OnState doesn't have a OnButtonPressed event so it doesn't need to be implemented
    public override void OnButtonPressed(MediaPlayer player)
    {
        throw new NotImplementedException();
    }

    //When hitting the offbutton in the OnState, the new state is End
    public override void OffButtonPressed(MediaPlayer player)
    {
        player.state = new End();
    }

    //The OnState itself containts 3 events, therefore these need to be implemented by every state whitin the OnState state
    public abstract class SubStates : MediaPlayerStates
    {
        public abstract void DeviceBooted(MediaPlayer player);
        public abstract void PlayButtonPressed(MediaPlayer player);
        public abstract void StopButtonPressed(MediaPlayer player);
    }

    //The OnStartState is the pseudoState where the On state starts
    public class OnStartState : SubStates
    {
        //When booted, the state of the player changes to the ShowMediaFileState state
        public override void DeviceBooted(MediaPlayer player)
        {
            player.state = new ShowMediaFileState();
        }

        //The events below don't need to be implemented since they don't exist. 
        public override void PlayButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        public override void StopButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        public override void OnButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        public override void OffButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }
    }

    public class ShowMediaFileState : SubStates
    {
        //This event doesn't exists for this state
        public override void DeviceBooted(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        //When hitting the play button in this state, play the mediafile
        public override void PlayButtonPressed(MediaPlayer player)
        {
            player.state = new PlayMediaFileState();
        }

        //These events also don't exist for this state
        public override void StopButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        public override void OnButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        public override void OffButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }
    }

    public class PlayMediaFileState : SubStates
    {
        //This event doesn't exist for this state
        public override void DeviceBooted(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        //This event doesn't exist for this state
        public override void PlayButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        //While playing a file and hitting the stopbutton, the state changes to the ShowMediaFileState state
        public override void StopButtonPressed(MediaPlayer player)
        {
            player.state = new ShowMediaFileState();
        }

        //This event doesn't exist for this state
        public override void OnButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }

        //This event doesn't exist for this state
        public override void OffButtonPressed(MediaPlayer player)
        {
            throw new NotImplementedException();
        }
    }
}

//The endstate doesn't need any implementation since there cannot occur a event while being off
public class End : MediaPlayerStates
{
    public override void OnButtonPressed(MediaPlayer player)
    {
        throw new NotImplementedException();
    }

    public override void OffButtonPressed(MediaPlayer player)
    {
        throw new NotImplementedException();
    }
}

在定义MediaPlayer类中的事件时,我只能调用以下函数:

  • OnButtonPressed
  • OffButtonPressed

所以我想知道,我的实现方式是否正确?哪里出了错?我也尝试了使用组合模式的建议,但是我不明白它应该如何与状态模式一起使用。希望有人可以帮忙!


你考虑过使用IEnumerable和yield吗?它们在语言内部提供了简单的状态机制。例如:http://www.yoda.arachsys.com/csharp/csharp2/iterators.html(网络上的众多示例之一) - Will
1
据我所知,你的建议不是一个好主意。因为我对这个概念还很新,所以我搜索了一下,找到了这个链接:https://dev59.com/nXM_5IYBdhLWcg3w3nNs不过,我还是很感谢你的建议 :) - user341877
4个回答

3
我认为你也需要使用Composite,这将允许你将状态机链接在一起。

2
使用状态模式制作HSM时,每个具有子状态的状态本身必须是一个状态机。这样,上层就不会知道子状态(副作用更少),状态可以更好地管理其子状态(可以有默认状态,可以记住它上次处于的状态等)。
另外,当您无法对操作执行任何有用的操作时,抛出异常是错误的。您应该忽略它。只有在异常情况下才会抛出异常,按错按钮是预期的用户行为。

+1:“……按错按钮是预期的用户行为。”这很正常。极少数情况下会有例外——我希望程序员能理解这一点! - quamrana
不能同意“当用户按下错误的按钮时,你应该忽略它”的概括。我真的很讨厌那种在我明明按对了按钮的情况下什么都不告诉我的应用程序。作为用户,我应该被告知我做错了什么,而不必在互联网上寻找解决方案...更不用说NotImplementedException是编写代码的正确方式——显然它还没有完成,作为程序员,你不想忘记可能导致数据损坏的场景。 - mojmir.novak
我并不是在概括,我只是在谈论这个具体的例子。 我的偏好是禁用无用的按钮。在这种情况下,未实现的异常是一种滥用,因为它永远无法实现。 - Glenner003

1
为了以通用的方式使其工作,您需要将状态机的层次结构视为树形结构; 节点之间的转换可以使用树的最近公共祖先(LCA)算法,然后从源节点祖先下的公共祖先以下的节点退出(将退出级联到任何子节点),然后从公共祖先以下的节点进入目标节点祖先中的每个节点到目标节点,最后,如果目标节点有子节点,您需要像进入任何其他复合状态一样进入这些节点。

这是在UML超级结构规范中提到的方法。

请查看https://github.com/steelbreeze/state.cs中的源代码,因为它实现了上述方法。

要查看工作示例,请查看姐妹JavaScript版本的项目网站:http://www.steelbreeze.net/state.js/


1

在开始实现自己的FSM框架之前,先看看SMC - 状态机编译器。

SMC采用状态机的文本定义并生成实现它的代码。它具有广泛的语言后端,包括C#。它还可以输出dot文件以生成FSM的图表。

SMC可以创建类似于分层状态机的东西,使用push和pop转换 - push将控制传输到新的状态机,而pop将控制返回到原始状态机。


无论一个可以生成FSM的框架看起来多么棒,我宁愿尝试创建自己的FSM。此外,我不想在我的项目中有很多框架文件。我只想创建一个简单而干净的FSM。 - user341877

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接