C'est un peu de l'auto-promo éhontée ici, mais il y a quelque temps j'ai créé une bibliothèque appelée YieldMachine qui permet de décrire une machine à états de complexité limitée de manière très propre et simple. Prenons l'exemple d'une lampe :
Remarquez que cette machine à états a 2 triggers et 3 états. Dans le code de la YieldMachine, nous écrivons une seule méthode pour tous les comportements liés aux états, dans laquelle nous commettons l'horrible atrocité d'utiliser la méthode goto
pour chaque état. Un déclencheur devient une propriété ou un champ de type Action
décoré d'un attribut appelé Trigger
. J'ai commenté le code du premier état et de ses transitions ci-dessous ; les états suivants suivent le même schéma.
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();
}
}
Court et agréable, hein !
Cette machine à états est contrôlée simplement en lui envoyant des déclencheurs :
var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off
sm.PressSwitch(); //go on
sm.GotError(); //get error
sm.PressSwitch(); //go off
Juste pour clarifier, j'ai ajouté quelques commentaires au premier état pour vous aider à comprendre comment l'utiliser.
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
...
Cela fonctionne parce que le compilateur C# a créé en interne une machine à états pour chaque méthode qui utilise la fonction yield return
. Cette construction est généralement utilisée pour créer paresseusement des séquences de données, mais dans ce cas, nous ne sommes pas réellement intéressés par la séquence retournée (qui est de toute façon entièrement nulle), mais par le comportement de l'état qui est créé sous le capot.
Le site StateMachine
La classe de base effectue une réflexion sur la construction pour attribuer du code à chaque [Trigger]
qui définit l'action Trigger
et fait avancer la machine à états.
Mais vous n'avez pas vraiment besoin de comprendre les mécanismes internes pour pouvoir l'utiliser.
0 votes
Vous vous interrogez sur les machines à états en général ou seulement sur celles basées sur des itérateurs ?
2 votes
Il existe une librairie Stateless .Net Core avec des exemples, des DAGs, des daigrammes, etc. - Cela vaut la peine de l'examiner : hanselman.com/blog/