Как лучше всего использовать иерархический конечный автомат с использованием шаблона состояний?

Я собираюсь реализовать на C # иерархический конечный автомат с использованием шаблона состояний. В качестве руководства я использую этот пример. Однако этот пример не дает ответа относительно иерархических состояний. К сожалению, мне кажется, что нигде не найду хороших примеров. Моя первая мысль - создать вложенные классы для иерархических состояний. Но считается ли это лучшей практикой или есть лучшие решения?

Приветствую!

ОБНОВЛЕНИЕ:

Я весь день сижу, пытаясь реализовать шаблон состояний, как описано выше. HSM основан на очень простом медиаплеере:

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

Так что мне интересно, хороша ли моя реализация? Что не так? Я также попытался рассмотреть предложение об использовании составного шаблона, но я не понимаю, как его следует использовать с шаблоном состояния. Надеюсь, что кто-нибудь сможет помочь!


person user341877    schedule 15.08.2010    source источник
comment
Вы рассматривали IEnumerable и yield? Они обеспечивают простую механику конечного автомата прямо в языке. например. yoda.arachsys.com/csharp/csharp2/iterators.html ( один из многих примеров в сети)   -  person Will    schedule 15.08.2010
comment
Насколько я понимаю, ваше предложение не очень хорошая идея. Поскольку я новичок в этой концепции, я искал ее и нашел следующее: stackoverflow.com/questions/1194853/ Тем не менее, я ценю ваш вклад :)   -  person user341877    schedule 15.08.2010


Ответы (4)


Думаю, вам тоже захочется Composite; это позволит вам связать конечные автоматы вместе.

person duffymo    schedule 15.08.2010

Для создания HSM с шаблоном состояний каждое состояние с подсостояниями должно быть самим конечным автоматом. Таким образом, верхний уровень не знает о подсостояниях (меньше побочных эффектов), и состояние может лучше управлять своими подсостояниями (может иметь состояние по умолчанию, может помнить последнее состояние, в котором оно было и т. Д.).

Кстати, генерировать исключение, когда вы не можете сделать что-либо полезное с действием, неправильно. Вы должны просто игнорировать это. Вы вызываете исключения только в исключительных случаях, нажатие неправильной кнопки является ожидаемым поведением пользователя.

person Glenner003    schedule 10.02.2011
comment
+1: ... нажатие не той кнопки - ожидаемое поведение пользователя. Это вполне нормально. Исключительных случаев очень мало - хотелось бы, чтобы программисты это понимали! - person quamrana; 19.11.2011
comment
Не могу согласиться с обобщением. Вы должны просто игнорировать, когда пользователь нажимает неправильную кнопку ... Я действительно ненавижу такие приложения, которые ничего не говорят мне, когда я нажимаю явно хорошую кнопку по уважительной причине. Как пользователь, я должен быть проинформирован о том, что я делаю неправильно, без необходимости искать решение в Интернете ... Не говоря уже о том, что NotImplementedException - это правильный способ написания кода - он, очевидно, еще не закончен, и как программист вы не хочу забывать сценарий, который может привести к повреждению данных. - person mojmir.novak; 17.08.2019
comment
Я не обобщаю, я говорю именно об этом примере. Я бы предпочел отключить неиспользуемые кнопки. В этом случае нереализованное исключение было злоупотреблением, потому что оно никогда не могло быть реализовано. - person Glenner003; 18.08.2019

Прежде чем приступить к реализации своей собственной структуры FSM, взгляните на SMC - компилятор конечного автомата.

SMC берет текстовое определение конечного автомата и генерирует код для его реализации. Он имеет бэкенды для широкого спектра языков, включая C #. Он также может выводить точечные файлы для создания диаграммы конечного автомата.

SMC может создавать что-то похожее на иерархические конечные автоматы с переходами push и pop - по сути, push передает управление новому конечному автомату, а pop возвращает управление исходному конечному автомату.

person Dave Kirby    schedule 15.08.2010
comment
Каким бы прекрасным ни был фреймворк, который может генерировать конечный автомат, я бы предпочел попробовать создать свой собственный конечный автомат. Кроме того, я не хочу, чтобы в моем проекте было много файлов фреймворка. Я просто хочу создать простой и чистый автомат. - person user341877; 15.08.2010

Чтобы заставить его работать в общем виде, вам нужно рассматривать иерархию конечного автомата как древовидную структуру; переходы между узлами могут использовать алгоритм наименьшего общего предка (LCA) для деревьев, затем выйти из узла ниже общего предка в родословной исходного узла (каскадный выход на любые дочерние узлы), затем ввести каждый узел в родословной целевого узла из узел ниже общего предка целевого узла, наконец, если целевой узел имеет дочерние элементы, вам нужно будет ввести их, как если бы вы входили в любое другое составное состояние.

Это метод, упомянутый в спецификации надстройки UML.

Взгляните на исходный код в https://github.com/steelbreeze/state.cs поскольку это реализует описанный выше метод.

Чтобы увидеть рабочий пример, взгляните на сайт проекта для родственной версии JavaScript здесь: http://www.steelbreeze.net/state.js/

person Mesmo    schedule 08.06.2013