配置状态机_配置简单状态机

配置状态机

介绍 (Introduction)

Complex systems can often be simplified by breaking them down into a series of discrete stages or states with transitions from state to state occurring as the system progresses. A state machine's function is to manage these transitions in response to some sort of input trigger. Ideally, the state machine should know very little about the states and their functionality, it just keeps a reference to the current state and directs all input towards that state. When a transition is made to another state, that state becomes the current state and inputs are directed towards it. The input triggers determine the next selected state, so, if modelling the production of a widget, the quality-control state could transition to the dispatch state or recycling state depending on the trigger received. To illustrate this, have a look at the UML State Machine Diagram of one of the purest forms of the state machine.

复杂的系统通常可以通过将它们分解为一系列离散的阶段或状态来简化,随着系统的发展,状态之间会发生过渡。 状态机的功能是响应某种输入触发来管理这些转换。 理想情况下,状态机应该对状态及其功能了解甚少,它仅保留对当前状态的引用,并将所有输入定向到该状态。 当转换到另一个状态时,该状态变为当前状态,并且输入指向该状态。 输入触发器确定下一个选定的状态,因此,如果对微件的生产建模,则质量控制状态可能会转换为调度状态或回收状态,具体取决于收到的触发器。 为了说明这一点,请查看状态机最纯形式之一的UML状态机图

Image 1

祖先状态机 (The Ancestral State Machine)

This is the basic form of the state machine from which modern versions have evolved. There are only two triggers, a binary 1 or 0. The way to read the diagram is to start at the starting state and follow the transitions from state to state. State A is the starting state, the initial current state. It's marked by having an arrow pointing to it that doesn't come from another state. If this state receives a 0, there is no transition as indicated by the arrow pointing back towards its origin. A 1 causes the machine to make state B the current state. The inputs are now applied to state B where the response is different. A 0 trigger here causes a transition back to state A and a 1 causes a transition to state C. From state C, a 1 goes to state D and a 0 to state A. State D is the end state, it has no transitions to another state. End states are marked by two concentric circles, there can be more than one end state but there is only one start state. So what's the machine doing? It's detecting the pattern 111 within a string of binary inputs. If, at the end of the data stream, the current state is D the input is accepted, if not it's rejected. Notice that there's no memory in the system, the states are unaware of each other and all transitions are triggered by external inputs. The states have no intrinsic functionality; they only act as a target identifier for the triggers.

这是状态机的基本形式,现代版本是从状态机演变而来的。 只有两个触发器,即二进制1或0。读取图表的方法是从起始状态开始,并遵循从一个状态到另一个状态的转换。 状态A是起始状态,即初始当前状态。 它的标志是有一个指向它的箭头,该箭头不是来自其他状态。 如果此状态接收到0,则没有过渡,如指向其起点的箭头所示。 A 1使机器将状态B设为当前状态。 现在,将输入应用于响应不同的状态B。 这里的0触发器将导致返回状态A的转换,而1将导致向状态C的转换。从状态C,a进入状态D,从0到状态A。状态D是结束状态,它没有转换为状态另一种状态。 结束状态由两个同心圆标记,可以有多个结束状态,但只有一个开始状态。 那机器在做什么呢? 它正在检测一串二进制输入中的模式111。 如果在数据流的末尾,当前状态为D,则接受输入,否则,输入被拒绝。 请注意,系统中没有内存,状态彼此之间不知道,并且所有转换都由外部输入触发。 状态没有内在的功能。 它们仅充当触发器的目标标识符。

无状态简介 (A Brief Introduction to Stateless)

The following examples use the popular state machine Stateless that's available for download as a NuGet package. The machine itself is generic, you need to provide the type of the States and the type of the Triggers as well as the starting State when it is initialised. Simple applications usually define an enum to represent the States and another enum to represent the Triggers. The enums are then used as labels to relate to internally managed pseudo-states and triggers. This arrangement removes the need to define a common interface for all states - a difficult task at the best of times as the whole point of transitioning to a new state is to implement a different functionality.

以下示例使用了流行的状态机Stateless ,该状态机可以作为NuGet软件包下载。 机器本身是通用的,因此在初始化时,您需要提供状态的类型,触发器的类型以及起始状态。 简单的应用程序通常定义一个enum来表示状态,并定义另一个enum来表示触发器。 然后将enum用作与内部管理的伪状态和触发器相关的标签。 这种安排消除了为所有状态定义通用接口的需要-最好的情况下,这是一项艰巨的任务,因为过渡到新状态的关键是实现不同的功能。

使用简单状态机验证电子邮件地址 (Verifying an Email Address Using a Simple State Machine)

This example shows how the basic state machine can be expanded to enable it to verify an email address. The context class is EmailValidator, it encapsulates the state machine and takes a string inputted from an external source. The string is iterated over and its characters are used as triggers, the string is verified if the machine ends up in an acceptable state when the iteration has finished. The triggers are chars and the States are an enum.

此示例显示了如何扩展基本状态机以使其能够验证电子邮件地址。 上下文类是EmailValidator ,它封装状态机并接受从外部源输入的字符串。 迭代字符串,并将其字符用作触发器,如果​​迭代结束后计算机最终处于可接受的状态,则验证字符串。 触发因素是chars ,状态是enum

public enum EmailState
{
    Start,
    Local,
    Domain,
    Accepted,
    Rejected
}

public class EmailValidator : IValidator
{
 private readonly StateMachine<EmailState, char> machine;
 public EmailValidator()
 {
   machine = new StateMachine<EmailState, char>(EmailState.Start);
   // ignore unconfigured Trigger exception
   machine.OnUnhandledTrigger((state, trigger) => { });
   ConfigureMachine();
}


To simplify matters, all illegal characters are filtered out before triggering the machine so the machine can concentrate on determining if an email address is correctly formatted.

为简化起见,在触发机器之前,所有非法字符都会被过滤掉,因此机器可以集中精力确定电子邮件地址的格式是否正确。

 public bool Validate(string dataString)
 {
  char[] acceptable = new char[] { '@', '.', '-' };
  //rinse out all illegal chars
  if (dataString.Any(c => !char.IsLetterOrDigit(c) && !acceptable.Contains(c))
   {
    return false;
   }

  foreach (var c in dataString)
  {
   //use the trigger 'x' for all alphanumeric chars
   char  trigger = char.IsLetterOrDigit(c) ? 'x' : c;
   //The Fire method initiates the state transition.
   machine.Fire(trigger);
  }
   var isValid = machine.IsInState(EmailState.Accepted);
   //reset to Start
   currentState = EmailState.Start;
   return isValid;
}


Image 2

The state machine diagram above shows how the machine is 'wired up'. The first character is inputted into the Start state. The characters @, dot and hyphen are rejected; all others are accepted and result in a transition to the Local state that deals with the local part of the email address. The Local state accepts all chars apart from @, @ causes a transition to the Domain state. The first character in the Domain state has to be alphanumeric. The Accepted state accepts everything apart from a hyphen and @.

上面的状态机图显示了如何“连接”机器。 第一个字符输入到开始状态。 字符@,点和连字符被拒绝; 所有其他内容均被接受,并导致过渡到处理电子邮件地址本地部分的本地状态。 本地状态接受@以外的所有字符,@导致转换为域状态。 域状态中的第一个字符必须为字母数字。 接受状态接受除连字符和@之外的所有内容。

配置状态机 (Configuring the State Machine)

States are configured by simply permitting the transitions that are allowed from each state using the Permit method that takes a trigger and a state as parameters.

通过使用Permit方法(将触发器和状态作为参数),简单地允许从每个状态允许的转换来配置状态。

private void ConfigureMachine()
       {
           machine.Configure(EmailState.Start)
           .Permit('@', EmailState.Rejected)
           .Permit('.', EmailState.Rejected)
           .Permit('x', EmailState.Local);

           machine.Configure(EmailState.Local)
           .Permit('@', EmailState.Domain);
           .......
        }


状态机图 (State Machine Diagrams)

State Machine diagrams are a great debugging aid as they enable you to visualise the machine's configuration to the extent that someone with no prior knowledge of the configuration can see exactly how the machine is set up. Stateless has a method, UmlDotGraph.Format(machine.GetInfo()), that outputs a Dot formatted string which, when pasted into the text box at Webgraphviz, will generate a graph. There is a free app available from the site that you can download to do the same thing.

状态机图是一种很好的调试辅助工具,它使您可以可视化机器的配置,以至于对配置没有任何先知的人可以确切地看到机器的设置方式。 Stateless有一个方法UmlDotGraph.Format(machine.GetInfo()) ,该方法输出一个Dot格式的字符串,将其粘贴到Webgraphviz的文本框中时,将生成一个图形。 该站点提供了一个免费的应用程序,您可以下载该应用程序以执行相同的操作。

使用状态来处理数据 (Using States to Process Data)

In the previous examples, the states are silent, they don't actually do any work. But, for states to be used in some form of production line, each state needs to be able to carry out work under the direction of the Context class. The way this is achieved in Stateless is by the use of two Action delegates, the OnEntry and OnExit Action delegates. OnEntry is called when the state is transitioned into and the OnExit method is called when the state is transitioned out of. It may be thought that the Action delegates would not be very useful as they have no parameters and return void. But that's not the case as the delegates are instantiated in the Context and so they are able to capture all of the Context's public and private variables.

在前面的示例中,状态是静默的,实际上它们没有做任何工作。 但是,要使状态以某种形式在生产线中使用,每个状态都必须能够在Context类的指导下进行工作。 在无状态状态下,通过使用两个Action委托( OnEntryOnExit Action委托)来实现此目的。 当状态转换成状态时,调用OnEntry ;当状态转换成状态时, OnExit方法。 可以认为Action委托不是很有用,因为它们没有参数并且返回void 。 但是情况并非如此,因为委托是在Context中实例化的,因此他们能够捕获Context的所有publicprivate变量。

private readonly IValidator validator = new EmailValidator();
 .....
 machine.Configure(State.Validating)
 .OnEntry(() =>
   {
     //prompt for email address
     var address= Console.ReadLine();
     Trigger trigger = validator.Validate(address) ? Trigger.Accept : Trigger.Fail;
     machine.Fire(trigger);
   })
 ....


An important point is that the Validator knows nothing about the State or the machine's OnEntry method that's associated with the State. In order to maintain a good separation of concerns, all transitions should be handled by the Context. It's not a good idea to allow any helper class such as the Validator to become trigger happy and start firing on its own volition.

重要的一点是, Validator对状态或与状态关联的计算机的OnEntry方法一无所知。 为了保持关注点之间的良好分离,所有过渡都应由Context处理。 不允许任何帮助器类(例如Validator变得触发快乐并开始自行发动,这不是一个好主意。

验证示例 (A Validation Example)

Image 3

This example mimics the some sort of validation process where the applicant has three chances to input a valid email address. After every unsuccessful attempt, the user has the option to cancel or retry. Three failed attempts result in the application being rejected. The Validating state is configured to use the trigger Fail as a guard trigger. Its transition depends upon the bool returned from the IsRejected method. If IsRejected returns true, the trigger Fail causes a transition to the Rejected end state. A false value causes a transition to the Failed state where there is an option to cancel or to try again.

此示例模仿某种验证过程,其中申请人有三次输入有效电子邮件地址的机会。 每次尝试失败后,用户可以选择取消或重试。 三次尝试失败均导致该应用程序被拒绝。 Validating状态配置为使用触发器Fail作为保护触发器。 其转换取决于从IsRejected方法返回的bool 。 如果IsRejected返回true ,则触发器Fail会导致转换为Rejected结束状态。 false值将导致转换为“ Failed状态,其中可以选择取消或重试。

machine.Configure(State.Validating)
.OnEntry(()
{
    //prompt for an input
    Tweet(Constants.StartValidating);
    var address= Console.ReadLine();
    Trigger trigger = validator.Validate(address) ? Trigger.Accept : Trigger.Fail;
    machine.Fire(trigger);
})
.Permit(Trigger.Accept, State.Accepted)
.PermitIf(Trigger.Fail, State.Failed, () => !IsRejected)
.PermitIf(Trigger.Fail, State.Rejected, () => IsRejected);


Guard triggers are configured using the PermitIf method. My preference is not to use them and to keep all the logic within the OnEntry Action rather than let it escape into some sort of guided missile type trigger that hasn't got a predefined target.

使用PermitIf方法配置保护触发器。 我的首选是不要使用它们并将所有逻辑保留在OnEntry Action而不是让它逃脱某种没有预定目标的制导导弹类型的触发器。

管理当前状态 (Managing the Current State)

In the validation example, the current state needs to be managed externally to the StateMachine class so that it can be reset to the Start state before each attempt at validation. Here's how to set it up, it's just a matter of providing a getter and setter as parameters to the constructor.

在验证示例中,当前状态需要在StateMachine类的外部进行管理,以便可以在每次尝试验证之前将其重置为Start状态。 这是设置方法,只需将getter和setter作为参数提供给构造函数即可。

private EmailState currentState= EmailState.Start;
private readonly StateMachine<EmailState, char> machine;
public EmailValidator()
 {
  //provide a getter and setter so the currentState can be reset to the Start State
  //after each attempt at validation
  machine = new StateMachine<EmailState, char>(() => currentState, s => currentState = s);
  // ignore unconfigured Trigger exception
  machine.OnUnhandledTrigger((state, trigger) => { });
  ConfigureMachine();
 }


子状态 (Substates)

Image 4

It's possible to designate a state as being a substate of some other state. The effect of this is that the superstate's OnExit method is not called when the current state transitions from the superstate to the substate. In this example, SeatBelt is a substate of Motoring, Engine is a substate of SeatBelt and Brake is a substate of Engine. The substate status is inherited so SeatBelt, Engine and Brake are all substates of Motoring. When the Park trigger is fired, OnExit methods will be called consecutively, bubbling up from the Brake state to the Motoring state. Typically, there would just be the one external transition permitted into the Motoring state and its substates but there can be multiple exit triggers.

可以将一个状态指定为某个其他状态的子状态。 这样的效果是,当当前状态从超级状态转换为子状态时,不会调用超级状态的OnExit方法。 在此示例中, SeatBeltMotoring的子状态, EngineSeatBelt的子状态, BrakeEngine的子状态。 子状态的状态是继承的,因此SeatBeltEngineBrake都是Motoring子状态。 触发Park触发器时,将连续调用OnExit方法,从Brake状态冒泡到Motoring状态。 通常,只有一个外部转换被允许进入Motoring状态及其子状态,但是可以有多个退出触发器。

  private void ConfigureMachine()
        {
            machine.Configure(State.Start)
            .Permit(Trigger.Motor, State.Motoring)
            .OnEntry(() => Console.WriteLine("In State Start"))
            .OnExit(() => Console.WriteLine("Leaving Start"));

            machine.Configure(State.Motoring)
            .Permit(Trigger.Fasten, State.Seatbelt)
            .OnEntry(() => Console.WriteLine("Started Motoring"))
            .OnExit(() => Console.WriteLine("Finished Motoring"));

            machine.Configure(State.Seatbelt)
            .SubstateOf(State.Motoring)
            .Permit(Trigger.Engage, State.Engine)
            .OnEntry(() => Console.WriteLine("Seatbelt Fastened"))
            .OnExit(() => Console.WriteLine("Seatbelt Unfastened"));


            machine.Configure(State.Engine)
            .SubstateOf(State.Seatbelt)
            .Permit(Trigger.Release, State.Brake)
            .OnEntry(() => Console.WriteLine("Engine Started"))
            .OnExit(() => Console.WriteLine("Engine Off"));

            machine.Configure(State.Brake)
            .SubstateOf(State.Engine)
            .Permit(Trigger.Park, State.Parked)
            .OnEntry(() => Console.WriteLine("Brake Released"))
            .OnExit(() => Console.WriteLine("Brake Applied"));

            machine.Configure(State.Parked)
           .OnEntry(() => Console.WriteLine("Parked"));
}

异步OnExitAsync和OnEntryAsync操作 (Asynchronous OnExitAsync and OnEntryAsync Actions)

The previous example could be refined by keeping the engine running and only stop it after the brakes have been applied. To do this, the Engine state needs to remain active when it's no longer the current state. So there's a need to run the Engine's OnEntry Action asynchronously and to end it when the state's OnExit method is called. The way to achieve this is to use the OnExitAsync Func and the FireAsync method.

可以通过保持发动机运转并仅在施加制动后才停止发动机来完善前面的示例。 为此,当Engine状态不再是当前状态时,它需要保持活动状态。 因此,需要异步运行Engine的OnEntry Action并在调用状态的OnExit方法时结束它。 实现此目的的方法是使用OnExitAsync FuncFireAsync方法。

 CancellationTokenSource cts = new CancellationTokenSource();
.....
          machine.Configure(State.Engine)
          .SubstateOf(State.Seatbelt)
          .Permit(Trigger.Release, State.Brake)
          .OnEntry( () =>
          {
              //start the task but don't await it here
              engineTask = Task.Run(()=>ChugChug(cts.Token));
              Log($"Engine Started {engineNoise}");
          })
          .OnExitAsync(async() =>
          {
            cts.Cancel();
            await engineTask;
            Log("Engine Stopped");
          });


The ChugChug method is just a bit of nonsense that simulates the engine running.

ChugChug方法只是一句废话,它可以模拟引擎的运行。

private void ChugChug(CancellationToken token)
{
    while (true)
    {
        //simulate long-running method
       Thread.Sleep(5);
        //check for cancellation
        if (token.IsCancellationRequested) break;
        Console.ForegroundColor = ConsoleColor.White;
        Console.Write(engineNoise);
    }
}


When there is a transition from the Brake state to the Parked state, the OnExitAsync Func of the current state is called first, then the call bubbles up through the substates to the Motoring state. So the OnExitAsync Func of all the Motoring states except Engine need to be configured in a similar way to this.

当存在从一个过渡Brake状态到Parked状态下, OnExitAsync Func的当前状态的首先被调用,则该呼叫经过子状态到冒泡Motoring状态。 所以OnExitAsync Func所有的Motoring除了状态Engine需要以类似的方式,以这种配置。

machine.Configure(State.Brake)
.SubstateOf(State.Engine)
.Permit(Trigger.Park, State.Parked)
.OnEntry(() => Log("Brake Released"))
.OnExitAsync(() =>
 {
     Log("BreakApplied");
     //the method expects a Task to be returned
     return Task.CompletedTask;
 });


The StartupAsync method transitions the current state from the Start state to the Parked state.

StartupAsync方法将当前状态从“ Start状态转换为“ Parked状态。

public async Task StartupAsync()
  {
     machine.Fire(Trigger.Motor);
     machine.Fire(Trigger.Fasten);
     machine.Fire(Trigger.Engage);
     machine.Fire(Trigger.Release);
     string msg = machine.IsInState(State.Motoring) ? "is in " : "is not in ";
     Log($"The current state is {machine.State}
         it {msg}state Motoring",ConsoleColor.Yellow);
     await Task.Delay(50);
     Log("\r\nFiring Trigger Park",ConsoleColor.Yellow);
      //FireAsync calls the OnExitAsync action of the current state
      //The call bubbles up through the substates to State.Motoring
      await machine.FireAsync(Trigger.Park);
  }


The output from StartupAsync is mainly frivolous but it gives an idea of how much functionality can be unleashed by simply firing a trigger.

StartupAsync的输出主要是轻浮的,但是它给出了一个简单的触发触发器就可以释放多少功能的想法。

Image 5

结论 (Conclusion)

State machines are useful for breaking the code up into a series of discrete sections and progressing the code from section to section. It’s true that configuring the machine needs some care and thought but, once set, it is unlikely to be corrupted by any other user-written code. The ability of state machines to produce a visual representation of the states and their transition triggers is a valuable asset as it provides a ‘wiring diagram’ of how the system is set up and greatly simplifies its maintenance and expansion. A state machine is not a panacea for all multiple pathway scenarios but it certainly knocks the IfThenElse pattern into a cocked hat.

状态机可用于将代码分解为一系列离散的部分,并使代码逐节进行。 确实,配置机器需要一定的注意和考虑,但是一旦设置,它就不会被任何其他用户编写的代码破坏。 状态机产生状态及其过渡触发器的直观表示的能力是一项宝贵的资产,因为它提供了系统设置的“接线图”,并大大简化了其维护和扩展。 状态机并不是所有多路径方案的灵丹妙药,但它肯定会使IfThenElse模式陷入困境。

翻译自: https://www.codeproject.com/Articles/5266755/Configuring-Simple-State-Machines

配置状态机

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值