Некоторая бесстыдная самореклама здесь, но некоторое время назад я создал библиотеку под названием YieldMachine , которая позволяет описать конечный автомат ограниченной сложности очень простым и понятным способом. Например, рассмотрим лампу:
Обратите внимание, что этот конечный автомат имеет 2 триггера и 3 состояния. В коде YieldMachine мы пишем единый метод для всего поведения, связанного с состоянием, в котором мы совершаем ужасное злодеяние использования goto
для каждого состояния. Триггер становится свойством или полем типа Action
, украшенным атрибутом с именем Trigger
. Я прокомментировал код первого состояния и его переходы ниже; следующие состояния следуют той же схеме.
public class Lamp : StateMachine
{
// Triggers (or events, or actions, whatever) that our
// state machine understands.
[Trigger]
public readonly Action PressSwitch;
[Trigger]
public readonly Action GotError;
// Actual state machine logic
protected override IEnumerable WalkStates()
{
off:
Console.WriteLine("off.");
yield return null;
if (Trigger == PressSwitch) goto on;
InvalidTrigger();
on:
Console.WriteLine("*shiiine!*");
yield return null;
if (Trigger == GotError) goto error;
if (Trigger == PressSwitch) goto off;
InvalidTrigger();
error:
Console.WriteLine("-err-");
yield return null;
if (Trigger == PressSwitch) goto off;
InvalidTrigger();
}
}
Коротко и красиво, а!
Этот конечный автомат управляется просто отправкой ему триггеров:
var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off
sm.PressSwitch(); //go on
sm.GotError(); //get error
sm.PressSwitch(); //go off
Просто чтобы уточнить, я добавил несколько комментариев к первому состоянию, чтобы помочь вам понять, как это использовать.
protected override IEnumerable WalkStates()
{
off: // Each goto label is a state
Console.WriteLine("off."); // State entry actions
yield return null; // This means "Wait until a
// trigger is called"
// Ah, we got triggered!
// perform state exit actions
// (none, in this case)
if (Trigger == PressSwitch) goto on; // Transitions go here:
// depending on the trigger
// that was called, go to
// the right state
InvalidTrigger(); // Throw exception on
// invalid trigger
...
Это работает, потому что компилятор C # фактически создал конечный автомат для каждого метода, который использует yield return
. Эта конструкция обычно используется для ленивого создания последовательностей данных, но в этом случае нас на самом деле не интересует возвращаемая последовательность (которая в любом случае равна нулю), а поведение поведения, которое создается внутри.
Базовый класс StateMachine
делает некоторые размышления о конструкции для назначения кода каждому действию [Trigger]
, которое устанавливает элемент Trigger
и перемещает конечный автомат вперед.
Но на самом деле вам не нужно понимать внутренности, чтобы иметь возможность его использовать.