State-machine - Stateless против традиционного кода if-else, трудно понять выгоды - PullRequest
2 голосов
/ 12 мая 2019

Недавно я столкнулся с грязным кодом if-else, поэтому я искал опции рефакторинга и нашел рекомендацию для state-machine в качестве элегантной замены для грязного if-else кода.Но что-то трудно понять: кажется, что я как клиент несу ответственность за перевод машины из одного состояния в другое.Теперь, если есть 2 варианта перехода (зависит от результата работы, выполненной в текущем состоянии). Нужно ли мне также использовать if-else?Если да, то в чем основная выгода от этого паттерна?С моей точки зрения, машина может автоматически переходить из исходного состояния

. Прежде чем спросить, я прочитал ниже, и это только укрепляет мое мнение:

Состояние автоматического продвижениямашина с Stateless

Как инкапсулировать конечный автомат .NET Stateless

Машина состояний, которая переходит в целевое состояние и запускает переходы и состояния между?

В моем примере у меня есть MarketPriceEvent, который необходимо сохранить в Redis.Перед сохранением он должен пройти путь проверки.Состояния пути проверки:

  • Базовая проверка
  • Сравнение
  • Другое сравнение
  • Хранение
  • Аудит ошибок

Проблема в том, что мне нужно принять много решений.Например: только если BasicValidation прошло успешно, я бы хотел перейти на Comparison.Теперь, если Comparison удалось, я бы хотел перейти к Storing, в противном случае перейти к ErrorAuditing.Итак, если мы собираемся в код:

 _machine.Configure(State.Validate).PermitIf(Trigger.Validated, State.Compare1, () => isValid);

        _machine.Configure(State.Compare1).OnEntry(CompareWithResource1).
            PermitIf(Trigger.Compared, State.Store, () => isValid)
            .PermitIf(Trigger.Compared, State.Compare2, () => !isValid);

И в моем коде клиента / оболочки я напишу:

//Stay at Validate state
        var marketPriceProcessingMachine = new MarketPriceProcessingMachine();

        if (marketPriceProcessingMachine.Permitted(Trigger.Validated))
                       marketPriceProcessingMachine.Fire(Trigger.Validated);
        //else 
        // ...

Короче говоря, если мне нужно использовать if-elseКакую выгоду я получил от такой концепции государственной машины?Если он детерминирован, почему он сам не переходит в следующее состояние?Если я не прав, что не так?

1 Ответ

1 голос
/ 13 мая 2019

Одним из преимуществ использования конечного автомата является то, что вы уменьшаете количество состояний, в которых может находиться объект. Я работал с кем-то, у кого было 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!");
        }

    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...