Одним из преимуществ использования конечного автомата является то, что вы уменьшаете количество состояний, в которых может находиться объект. Я работал с кем-то, у кого было 22 флага bool в одном классе. Было много если! (Что-то &&! Что-то еще ||! Пользователь щелкнул)…
Этот код трудно читать, трудно отлаживать, трудно тестировать, и более или менее невозможно рассуждать о том, каково состояние класса в действительности. 22 флага bool означают, что класс может быть в более чем 4 миллионах штатов Попробуйте сделать юнит-тесты для этого ...
Конечные автоматы могут уменьшить сложность кода, но это почти всегда усложняет работу в начале нового проекта. Тем не менее, в долгосрочной перспективе я обнаружил, что общая сложность оказывается в целом ниже. Это потому, что его легко расширять и добавлять больше состояний, поскольку уже определенные состояния можно оставить в покое.
За долгие годы я обнаружил, что ООП и конечные автоматы часто являются двумя аспектами одного и того же. И я также обнаружил, что ООП сложно, и его трудно получить «правильно».
Я думаю, что конечный автомат не должен быть виден снаружи объекта, включая его триггеры. Скорее всего, вы хотите иметь общедоступную государственную собственность только для чтения.
Я проектирую классы таким образом, чтобы вызывающая сторона не могла напрямую изменить состояние или позволяла вызывающей стороне напрямую вызывать метод Fire. Вместо этого я использую методы, которые являются глаголами, которые являются действиями, например, Validate ().
Ваш рабочий процесс нуждается в условных выражениях, но у вас есть некоторая свобода того, где их размещать. Я бы предложил отделить бизнес-логику от конфигурации конечного автомата. Я думаю, что это облегчает чтение конечного автомата.
Как насчет чего-то вроде этого:
namespace ConsoleApp1
{
using Stateless;
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Press Q to stop validating events");
ConsoleKeyInfo c;
do
{
var mpe = new MarketPriceEvent();
mpe.Validate();
c = Console.ReadKey();
} while (c.Key != ConsoleKey.Q);
}
}
public class MarketPriceEvent
{
public void Validate()
{
_machine.Fire(Trigger.Validate);
}
public enum State { Validate, Compare2, ErrorAuditing, Compare1, Storing }
private enum Trigger { Validate, CompareOneOk, CompareTwoOk, Error, }
private readonly StateMachine<State, Trigger> _machine;
public MarketPriceEvent()
{
_machine = new StateMachine<State, Trigger>(State.Validate);
_machine.Configure(State.Validate)
.Permit(Trigger.Validate, State.Compare1);
_machine.Configure(State.Compare1)
.OnEntry(DoEventValidation)
.Permit(Trigger.CompareOneOk, State.Compare2)
.Permit(Trigger.Error, State.ErrorAuditing);
_machine.Configure(State.Compare2)
.OnEntry(DoEventValidationAgainstResource2)
.Permit(Trigger.CompareTwoOk, State.Storing)
.Permit(Trigger.Error, State.ErrorAuditing);
_machine.Configure(State.Storing)
.OnEntry(HandleStoring);
_machine.Configure(State.ErrorAuditing)
.OnEntry(HandleError);
}
private void DoEventValidation()
{
// Business logic goes here
if (isValid())
_machine.Fire(Trigger.CompareOneOk);
else
_machine.Fire(Trigger.Error);
}
private void DoEventValidationAgainstResource2()
{
// Business logic goes here
if (isValid())
_machine.Fire(Trigger.CompareTwoOk);
else
_machine.Fire(Trigger.Error);
}
private bool isValid()
{
// Returns false every five seconds...
return (DateTime.UtcNow.Second % 5) != 0;
}
private void HandleStoring()
{
Console.WriteLine("Awesome, validation OK!");
}
private void HandleError()
{
Console.WriteLine("Oh noes, validation failed!");
}
}
}