C # में सरल स्टेट मशीन उदाहरण?


257

अपडेट करें:

उदाहरणों के लिए फिर से धन्यवाद, वे बहुत मददगार रहे हैं और निम्नलिखित के साथ मेरा मतलब यह नहीं है कि मैं उनसे कुछ भी दूर ले जाऊं।

वर्तमान में दिए गए उदाहरण नहीं हैं, जहां तक ​​मैं उन्हें और राज्य-मशीनों को समझता हूं, हम आमतौर पर एक राज्य-मशीन द्वारा जो भी समझते हैं, उसका केवल आधा हिस्सा?
इस अर्थ में कि उदाहरण राज्य बदलते हैं लेकिन यह केवल एक चर के मूल्य को बदलकर (और अलग-अलग राज्यों में अलग-अलग मूल्य-परिवर्तन की अनुमति देकर) का प्रतिनिधित्व करता है, जबकि आमतौर पर एक राज्य मशीन को भी व्यवहार में बदलाव करना चाहिए, और व्यवहार में (केवल) नहीं राज्य के आधार पर एक चर के लिए अलग-अलग मूल्य परिवर्तन की अनुमति देने की भावना, लेकिन विभिन्न राज्यों के लिए अलग-अलग तरीकों को निष्पादित करने की अनुमति देने के अर्थ में।

या क्या मुझे राज्य मशीनों और उनके सामान्य उपयोग की गलत धारणा है?

सादर


मूल प्रश्न:

मुझे यह चर्चा सी # में स्टेट मशीन और इटरेटर ब्लॉक के बारे में मिली। टूल्स और स्टेट मशीन बनाने के लिए टूल और लिए क्या नहीं है, इसलिए मुझे बहुत सार सामान मिला लेकिन एक नॉब के रूप में यह सब थोड़ा भ्रामक है।

इसलिए यह बहुत अच्छा होगा अगर कोई C # स्रोत कोड-उदाहरण प्रदान कर सकता है जो केवल 3,4 राज्यों के साथ एक साधारण राज्य मशीन का एहसास करता है, बस इसे पाने के लिए।



क्या आप सामान्य रूप से या सिर्फ इटैलर आधारित राज्य मशीनों के बारे में सोच रहे हैं?
स्कर्मेंडल

2
उदाहरणों के साथ .Net कोर स्टेटलेस लिबास है, डीएजी दायग्राम आदि - समीक्षा के लायक: hanselman.com/blog/…
zmische

जवाबों:


416

आइए इस सरल अवस्था आरेख से शुरू करें:

सरल राज्य मशीन आरेख

हमारे पास है:

  • 4 राज्य (निष्क्रिय, सक्रिय, रुके हुए, और बाहर)
  • 5 प्रकार के राज्य परिवर्तन (आरंभ कमान, अंत कमान, ठहराव कमान, फिर से शुरू कमान, निकास कमान)।

आप इसे C # में कई तरह से बदल सकते हैं, जैसे कि वर्तमान स्थिति और कमांड पर स्विच स्टेटमेंट करना, या ट्रांज़िशन टेबल में बदलाव देखना। इस सरल राज्य मशीन के लिए, मैं एक संक्रमण तालिका पसंद करता हूं, जिसका उपयोग करके प्रतिनिधित्व करना बहुत आसान है Dictionary:

using System;
using System.Collections.Generic;

namespace Juliet
{
    public enum ProcessState
    {
        Inactive,
        Active,
        Paused,
        Terminated
    }

    public enum Command
    {
        Begin,
        End,
        Pause,
        Resume,
        Exit
    }

    public class Process
    {
        class StateTransition
        {
            readonly ProcessState CurrentState;
            readonly Command Command;

            public StateTransition(ProcessState currentState, Command command)
            {
                CurrentState = currentState;
                Command = command;
            }

            public override int GetHashCode()
            {
                return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
            }

            public override bool Equals(object obj)
            {
                StateTransition other = obj as StateTransition;
                return other != null && this.CurrentState == other.CurrentState && this.Command == other.Command;
            }
        }

        Dictionary<StateTransition, ProcessState> transitions;
        public ProcessState CurrentState { get; private set; }

        public Process()
        {
            CurrentState = ProcessState.Inactive;
            transitions = new Dictionary<StateTransition, ProcessState>
            {
                { new StateTransition(ProcessState.Inactive, Command.Exit), ProcessState.Terminated },
                { new StateTransition(ProcessState.Inactive, Command.Begin), ProcessState.Active },
                { new StateTransition(ProcessState.Active, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Active, Command.Pause), ProcessState.Paused },
                { new StateTransition(ProcessState.Paused, Command.End), ProcessState.Inactive },
                { new StateTransition(ProcessState.Paused, Command.Resume), ProcessState.Active }
            };
        }

        public ProcessState GetNext(Command command)
        {
            StateTransition transition = new StateTransition(CurrentState, command);
            ProcessState nextState;
            if (!transitions.TryGetValue(transition, out nextState))
                throw new Exception("Invalid transition: " + CurrentState + " -> " + command);
            return nextState;
        }

        public ProcessState MoveNext(Command command)
        {
            CurrentState = GetNext(command);
            return CurrentState;
        }
    }


    public class Program
    {
        static void Main(string[] args)
        {
            Process p = new Process();
            Console.WriteLine("Current State = " + p.CurrentState);
            Console.WriteLine("Command.Begin: Current State = " + p.MoveNext(Command.Begin));
            Console.WriteLine("Command.Pause: Current State = " + p.MoveNext(Command.Pause));
            Console.WriteLine("Command.End: Current State = " + p.MoveNext(Command.End));
            Console.WriteLine("Command.Exit: Current State = " + p.MoveNext(Command.Exit));
            Console.ReadLine();
        }
    }
}

व्यक्तिगत पसंद की बात के रूप में, मैं अपनी राज्य मशीनों को नियत रूपGetNext से अगले राज्य को वापस करने के लिए एक फ़ंक्शन के साथ डिजाइन करना पसंद करता हूं , और राज्य मशीन को म्यूट करने के लिए एक फ़ंक्शन।MoveNext


65
GetHashCode()Primes का उपयोग करने के सही कार्यान्वयन के लिए +1 ।
a२

13
क्या आप मुझे GetHashCode () का उद्देश्य समझा सकते हैं?
सिद्धार्थ

14
@ सिद्धार्थ: StateTransitionकक्षा का उपयोग शब्दकोष में कुंजी के रूप में किया जाता है और चाबियों की समानता महत्वपूर्ण है। StateTransitionजब तक वे एक ही संक्रमण का प्रतिनिधित्व करते हैं (जैसे CurrentStateऔर Commandसमान हैं) तब तक दो अलग-अलग उदाहरणों को समान माना जाना चाहिए । समानता को लागू करने के लिए आपको और Equalsसाथ ही ओवरराइड करना होगा GetHashCode। विशेष रूप से शब्दकोश हैश कोड का उपयोग करेगा और दो समान वस्तुओं को एक ही हैश कोड वापस करना होगा। आपको अच्छा प्रदर्शन भी मिलता है यदि बहुत से गैर-समान ऑब्जेक्ट समान हैश कोड साझा नहीं करते हैं, जो कि GetHashCodeदिखाए गए अनुसार लागू किया गया है।
मार्टिन लीवरेज

14
हालांकि यह निश्चित रूप से आपको एक राज्य मशीन (और एक उचित C # 'ish कार्यान्वयन के रूप में अच्छी तरह से) हो जाता है, मुझे लगता है कि यह अभी भी ओपी के व्यवहार को बदलने के प्रश्न का उत्तर याद कर रहा है? आखिरकार, यह सिर्फ राज्यों की गणना करता है लेकिन राज्य परिवर्तनों से संबंधित व्यवहार, कार्यक्रम का वास्तविक मांस और आमतौर पर एंट्री / एक्जिट इवेंट कहा जाता है, अभी भी गायब है।

2
अगर किसी को इसकी आवश्यकता है तो: मैंने इस टेट मशीन को समायोजित किया और इसे अपने एकता के खेल में इस्तेमाल किया। यह git हब पर उपलब्ध है: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

आप मौजूदा खुले स्रोत Finite State Machines में से एक का उपयोग करना चाह सकते हैं। Eg bbv.Common.StateMachine http://code.google.com/p/bbvcommon/wiki/StateMachine पर मिला । इसमें एक बहुत ही सहज धाराप्रवाह वाक्य रचना और बहुत सारी विशेषताएं हैं जैसे, प्रवेश / निकास क्रिया, संक्रमण क्रिया, गार्ड, पदानुक्रमित, निष्क्रिय कार्यान्वयन (कॉलर के थ्रेड पर निष्पादित) और सक्रिय कार्यान्वयन (स्वयं का धागा जिस पर एफएमएस चलता है,) घटनाओं को एक कतार में जोड़ा जाता है)।

जूलियट का उदाहरण लेने से राज्य मशीन की परिभाषा बहुत आसान हो जाती है:

var fsm = new PassiveStateMachine<ProcessState, Command>();
fsm.In(ProcessState.Inactive)
   .On(Command.Exit).Goto(ProcessState.Terminated).Execute(SomeTransitionAction)
   .On(Command.Begin).Goto(ProcessState.Active);
fsm.In(ProcessState.Active)
   .ExecuteOnEntry(SomeEntryAction)
   .ExecuteOnExit(SomeExitAction)
   .On(Command.End).Goto(ProcessState.Inactive)
   .On(Command.Pause).Goto(ProcessState.Paused);
fsm.In(ProcessState.Paused)
   .On(Command.End).Goto(ProcessState.Inactive).OnlyIf(SomeGuard)
   .On(Command.Resume).Goto(ProcessState.Active);
fsm.Initialize(ProcessState.Inactive);
fsm.Start();

fsm.Fire(Command.Begin);

अद्यतन : परियोजना का स्थान इस तरह आगे बढ़ गया है: https://github.com/appccelerate/statemachine


4
इस उत्कृष्ट ओपन सोर्स स्टेट मशीन को संदर्भित करने के लिए धन्यवाद। क्या मैं पूछ सकता हूं कि मुझे वर्तमान स्थिति कैसे मिल सकती है?
रमजान पोलाट

2
आप नहीं कर सकते हैं और आपको नहीं करना चाहिए। राज्य कुछ अस्थिर है। जब आप राज्य से अनुरोध करते हैं तो यह संभव है कि आप संक्रमण के बीच में हों। सभी क्रियाएं संक्रमणों, राज्य प्रवेश और राज्य निकासों के भीतर की जानी चाहिए। यदि आप वास्तव में राज्य चाहते हैं, तो आप एक स्थानीय फ़ील्ड जोड़ सकते हैं और एक प्रविष्टि कार्रवाई में राज्य को असाइन कर सकते हैं।
रेमो ग्लोर

4
सवाल यह है कि आपको इसकी क्या "आवश्यकता" है और यदि आपको वास्तव में एसएम राज्य या किसी अन्य प्रकार के राज्य की आवश्यकता है। उदाहरण के लिए, यदि आपको कुछ प्रदर्शन पाठ की आवश्यकता है, तो कई में उदाहरण के लिए एक ही प्रदर्शन पाठ हो सकता है यदि भेजने की तैयारी में कई उप-अवस्थाएँ हों। इस मामले में आपको वही करना चाहिए जो आप करने का इरादा रखते हैं। कुछ डिस्प्ले टेक्स्ट को सही स्थानों पर अपडेट करें। जैसे ExecuteOnEntry के भीतर। यदि आपको अधिक जानकारी की आवश्यकता है, तो एक नया प्रश्न पूछें और अपनी समस्या के बारे में बताएं क्योंकि यह यहां विषय से हट रहा है।
रेमो ग्लोर

ठीक है मैं एक नया सवाल पूछ रहा हूं और आपको जवाब देने के लिए इंतजार कर रहा हूं। क्योंकि मुझे नहीं लगता कि किसी और ने इस समस्या को हल किया है क्योंकि आपके पास सबसे अच्छा जवाब है लेकिन फिर भी प्रश्नकर्ता ने स्वीकार नहीं किया। मैं यहाँ प्रश्न url पोस्ट करूँगा। धन्यवाद।
रमज़ान पोलाट

4
धाराप्रवाह और घोषणात्मक एपीआई के लिए +1। यह विस्मयकारी है। BTW, Google कोड पुराना लग रहा है। उनकी नवीनतम परियोजना स्थल GitHub पर है यहाँ
KFL

51

यहाँ एक बहुत ही क्लासिक परिमित स्टेट मशीन का उदाहरण दिया गया है, एक बहुत ही सरल इलेक्ट्रॉनिक उपकरण (जैसे टीवी)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace fsm
{
class Program
{
    static void Main(string[] args)
    {
        var fsm = new FiniteStateMachine();
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.PlugIn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOff);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.TurnOn);
        Console.WriteLine(fsm.State);
        fsm.ProcessEvent(FiniteStateMachine.Events.RemovePower);
        Console.WriteLine(fsm.State);
        Console.ReadKey();
    }

    class FiniteStateMachine
    {
        public enum States { Start, Standby, On };
        public States State { get; set; }

        public enum Events { PlugIn, TurnOn, TurnOff, RemovePower };

        private Action[,] fsm;

        public FiniteStateMachine()
        {
            this.fsm = new Action[3, 4] { 
                //PlugIn,       TurnOn,                 TurnOff,            RemovePower
                {this.PowerOn,  null,                   null,               null},              //start
                {null,          this.StandbyWhenOff,    null,               this.PowerOff},     //standby
                {null,          null,                   this.StandbyWhenOn, this.PowerOff} };   //on
        }
        public void ProcessEvent(Events theEvent)
        {
            this.fsm[(int)this.State, (int)theEvent].Invoke();
        }

        private void PowerOn() { this.State = States.Standby; }
        private void PowerOff() { this.State = States.Start; }
        private void StandbyWhenOn() { this.State = States.Standby; }
        private void StandbyWhenOff() { this.State = States.On; }
    }
}
}

6
राज्य मशीनों के लिए किसी भी नए के लिए, यह पहले पैरों को गीला करने का एक उत्कृष्ट पहला उदाहरण है।
पॉजिटिव

2
मैं राज्य मशीनों के लिए नया हूँ और गंभीरता से, इसने मुझे लाइट लाया है - धन्यवाद!
MC5

1
मुझे यह कार्यान्वयन पसंद आया। जो कोई भी इस पर ठोकर खा सकता है, उसके लिए एक मामूली "सुधार" है। FSM कक्षा में, मैंने private void DoNothing() {return;}null के सभी उदाहरणों को जोड़ा और प्रतिस्थापित किया this.DoNothing। वर्तमान स्थिति को लौटाने का सुखद दुष्प्रभाव है।
सेतमोमो

1
अगर इन नामों में से कुछ के पीछे कोई तर्क है तो मैं सोच रहा हूँ। जब मैं इसे देखता हूं, तो मेरा पहला अंतर्ज्ञान के तत्वों का नाम बदलना Statesहै Unpowered, Standby, On। मेरा तर्क यह है कि अगर कोई मुझसे पूछे कि मेरा टेलीविजन किस राज्य में है, तो मैं "ऑफ" कहूंगा और "स्टार्ट" नहीं। मैं भी बदल StandbyWhenOnऔर StandbyWhenOffकरने के लिए TurnOnऔर TurnOff। यह कोड को अधिक सहजता से पढ़ता है, लेकिन मुझे आश्चर्य होता है कि क्या सम्मेलन या अन्य कारक हैं जो मेरी शब्दावली को कम उपयुक्त बनाते हैं।
जेसन हम्जे

उचित लगता है, मैं वास्तव में किसी भी राज्य के नामकरण सम्मेलन का पालन नहीं कर रहा था; जैसा कि आप जो भी मॉडल बनाते हैं उसका नाम समझ में आता है।
20

20

यहां कुछ बेशर्म आत्म-प्रोमो, लेकिन कुछ समय पहले मैंने यिल्डाचाइन नामक एक पुस्तकालय बनाया, जो एक सीमित-जटिलता राज्य मशीन को बहुत साफ और सरल तरीके से वर्णित करने की अनुमति देता है। उदाहरण के लिए, एक दीपक पर विचार करें:

दीपक की राज्य मशीन

ध्यान दें कि इस राज्य मशीन में 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सदस्य और राज्य मशीन आगे बढ़ता है।

लेकिन आपको वास्तव में इसे उपयोग करने में सक्षम होने के लिए आंतरिक को समझने की आवश्यकता नहीं है।


2
"गोटो" केवल अत्याचार है अगर यह तरीकों के बीच कूदता है। सौभाग्य से, सी # में अनुमति नहीं है।
Brannon

अच्छी बात! वास्तव में, मैं बहुत प्रभावित होऊंगा यदि कोई सांख्यिकीय रूप से टाइप की गई भाषा एक gotoविधि के बीच अनुमति देने के लिए प्रबंधन करेगी ।
स्क्रेबेल

3
@ ब्रैनॉन: कौन सी भाषा gotoविधियों के बीच कूदने की अनुमति देती है? मैं नहीं देखता कि यह कैसे काम करेगा। नहीं, gotoसमस्याग्रस्त है क्योंकि यह प्रक्रियात्मक प्रोग्रामिंग में परिणाम देता है (यह अपने आप में इकाई परीक्षण जैसी अच्छी चीजों को जटिल करता है), कोड पुनरावृत्ति को बढ़ावा देता है (देखा गया है कि InvalidTriggerहर राज्य के लिए कैसे सम्मिलित किया जाना चाहिए?) और अंत में कार्यक्रम का पालन करना कठिन हो जाता है। इसकी तुलना इस धागे में (अधिकांश) अन्य समाधानों से करें और आप देखेंगे कि यह एकमात्र ऐसा है जहां संपूर्ण FSM एकल विधि में होता है। यह आमतौर पर एक चिंता बढ़ाने के लिए पर्याप्त है।
Groo

1
उदाहरण के लिए @Groo, GW-BASIC। यह मदद करता है कि यह तरीके, या यहां तक ​​कि कार्य नहीं करता है। इसके अलावा, मुझे यह समझने में बहुत कठिन समय है कि आप इस उदाहरण में "प्रोग्राम फ्लो का पालन करना कठिन क्यों पाते हैं"। यह एक राज्य मशीन है, "एक राज्य से दूसरे में जाना" केवल एक चीज है जो आप करते हैं। यह gotoबहुत अच्छी तरह से नक्शे ।
स्क्रेबेल

3
GW-BASIC gotoकार्यों के बीच कूदने की अनुमति देता है, लेकिन यह फ़ंक्शन का समर्थन नहीं करता है? :) आप सही हैं, "कठिन का पालन करें" टिप्पणी एक सामान्य gotoमुद्दा है, वास्तव में इस मामले में ज्यादा समस्या नहीं है।
ग्रू

13

आप एक इटरेटर ब्लॉक को कोड कर सकते हैं जो आपको ऑर्केस्ट्रेटेड फैशन में कोड ब्लॉक निष्पादित करने देता है। कैसे कोड ब्लॉक टूट गया है वास्तव में किसी भी चीज के अनुरूप नहीं है, यह सिर्फ यह है कि आप इसे कैसे कोड करना चाहते हैं। उदाहरण के लिए:

IEnumerable<int> CountToTen()
{
    System.Console.WriteLine("1");
    yield return 0;
    System.Console.WriteLine("2");
    System.Console.WriteLine("3");
    System.Console.WriteLine("4");
    yield return 0;
    System.Console.WriteLine("5");
    System.Console.WriteLine("6");
    System.Console.WriteLine("7");
    yield return 0;
    System.Console.WriteLine("8");
    yield return 0;
    System.Console.WriteLine("9");
    System.Console.WriteLine("10");
}

इस मामले में, जब आप काउंटटाउन कहते हैं, तो वास्तव में कुछ भी निष्पादित नहीं होता है, फिर भी। आपको जो मिलता है वह प्रभावी रूप से एक राज्य मशीन जनरेटर है, जिसके लिए आप राज्य मशीन का एक नया उदाहरण बना सकते हैं। आप GetEnumerator () कॉल करके ऐसा करते हैं। परिणामस्वरूप IEnumerator प्रभावी रूप से एक राज्य मशीन है जिसे आप MoveNext (...) कहकर ड्राइव कर सकते हैं।

इस प्रकार, इस उदाहरण में, पहली बार जब आप MoveNext (...) कहते हैं, तो आपको कंसोल में लिखा "1" दिखाई देगा, और अगली बार जब आप MoveNext (...) को कॉल करेंगे, तो आप 2, 3, 4, और देखेंगे फिर 5, 6, 7 और फिर 8, और फिर 9, 10. जैसा कि आप देख सकते हैं, यह ऑर्केस्ट्रेट करने के लिए एक उपयोगी तंत्र है कि चीजों को कैसे होना चाहिए।



8

मैं यहां एक और उत्तर पोस्ट कर रहा हूं क्योंकि यह एक अलग दृष्टिकोण से राज्य मशीनें हैं; बहुत दृश्य।

मेरा मूल उत्तर क्लासिक अनिवार्य कोड है। मुझे लगता है कि कोड के कारण इसकी काफी विजुअल होती है क्योंकि यह एरे के कारण होता है जो स्टेट मशीन को सरल बनाता है। नकारात्मक पक्ष यह है कि आपको यह सब लिखना होगा। रेमोस का उत्तर बॉयलर-प्लेट कोड लिखने के प्रयास को कम करता है लेकिन बहुत कम दृश्य है। तीसरा विकल्प है; वास्तव में राज्य मशीन ड्राइंग।

यदि आप .NET का उपयोग कर रहे हैं और रन टाइम के संस्करण 4 को लक्षित कर सकते हैं तो आपके पास वर्कफ़्लो की राज्य मशीन गतिविधियों का उपयोग करने का विकल्प है । इन सार में आप राज्य मशीन ( जूलियट आरेख में ज्यादा ) आकर्षित करते हैं और आपके लिए डब्ल्यूएफ रन-टाइम इसे निष्पादित करते हैं।

अधिक विवरण के लिए Windows वर्कफ़्लो फ़ाउंडेशन के साथ MSDN आलेख बिल्डिंग स्टेट मशीन देखें, और नवीनतम संस्करण के लिए यह कोडप्लेक्स साइट

यह विकल्प है। मैं हमेशा .NET को लक्षित करते समय पसंद करूंगा क्योंकि इसके प्रोग्रामर को देखना, बदलना और समझाना आसान है; चित्र एक हजार शब्दों के लायक हैं जैसा कि वे कहते हैं!


मुझे लगता है कि राज्य मशीन पूरे वर्कफ़्लो की नींव के सबसे अच्छे हिस्सों में से एक है!
फेबसेनेट

7

यह याद रखना उपयोगी है कि राज्य मशीनें अमूर्त हैं, और आपको एक बनाने के लिए किसी विशेष उपकरण की आवश्यकता नहीं है, हालांकि उपकरण उपयोगी हो सकते हैं।

आप उदाहरण के लिए कार्यों के साथ एक राज्य मशीन का एहसास कर सकते हैं:

void Hunt(IList<Gull> gulls)
{
    if (gulls.Empty())
       return;

    var target = gulls.First();
    TargetAcquired(target, gulls);
}

void TargetAcquired(Gull target, IList<Gull> gulls)
{
    var balloon = new WaterBalloon(weightKg: 20);

    this.Cannon.Fire(balloon);

    if (balloon.Hit)
    {
       TargetHit(target, gulls);
    }
    else
       TargetMissed(target, gulls);
}

void TargetHit(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("Suck on it {0}!", target.Name);
    Hunt(gulls);
}

void TargetMissed(Gull target, IList<Gull> gulls)
{
    Console.WriteLine("I'll get ya!");
    TargetAcquired(target, gulls);
}

यह मशीन गलियों का शिकार करती और उन्हें पानी के गुब्बारों से मारने की कोशिश करती। यदि यह याद आती है तो यह तब तक फायरिंग की कोशिश करेगा जब तक कि यह हिट न हो जाए (कुछ यथार्थवादी अपेक्षाओं के साथ कर सकता है;)), अन्यथा यह कंसोल में चमक जाएगा। यह शिकार करना जारी रखता है जब तक कि यह गलफड़ों से परेशान करने के लिए नहीं है।

प्रत्येक फ़ंक्शन प्रत्येक राज्य से मेल खाती है; शुरू और अंत (या स्वीकार करते हैं ) राज्यों को नहीं दिखाया गया है। हालांकि कार्यों द्वारा बनाए गए मॉडल की तुलना में संभवतः वहां अधिक राज्य हैं। उदाहरण के लिए, गुब्बारा फायर करने के बाद मशीन वास्तव में किसी अन्य स्थिति में थी जैसा कि पहले था, लेकिन मैंने तय किया कि यह अंतर बनाने के लिए अव्यावहारिक था।

एक सामान्य तरीका राज्यों का प्रतिनिधित्व करने के लिए कक्षाओं का उपयोग करना है, और फिर उन्हें विभिन्न तरीकों से कनेक्ट करना है।


7

इस महान ट्यूटोरियल को ऑनलाइन मिला और इसने मुझे अपने सिर को परिमित अवस्था मशीनों के चारों ओर लपेटने में मदद की।

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-implementation--gamedev-11867

ट्यूटोरियल भाषा अज्ञेय है, इसलिए इसे आसानी से आपकी सी # जरूरतों के लिए अनुकूलित किया जा सकता है।

इसके अलावा, उपयोग किए गए उदाहरण (भोजन की तलाश में एक चींटी) को समझना आसान है।


ट्यूटोरियल से:

यहां छवि विवरण दर्ज करें

public class FSM {
    private var activeState :Function; // points to the currently active state function

    public function FSM() {
    }

    public function setState(state :Function) :void {
        activeState = state;
    }

    public function update() :void {
        if (activeState != null) {
            activeState();
        }
    }
}


public class Ant
{
    public var position   :Vector3D;
    public var velocity   :Vector3D;
    public var brain      :FSM;

    public function Ant(posX :Number, posY :Number) {
        position    = new Vector3D(posX, posY);
        velocity    = new Vector3D( -1, -1);
        brain       = new FSM();

        // Tell the brain to start looking for the leaf.
        brain.setState(findLeaf);
    }

    /**
    * The "findLeaf" state.
    * It makes the ant move towards the leaf.
    */
    public function findLeaf() :void {
        // Move the ant towards the leaf.
        velocity = new Vector3D(Game.instance.leaf.x - position.x, Game.instance.leaf.y - position.y);

        if (distance(Game.instance.leaf, this) <= 10) {
            // The ant is extremelly close to the leaf, it's time
            // to go home.
            brain.setState(goHome);
        }

        if (distance(Game.mouse, this) <= MOUSE_THREAT_RADIUS) {
            // Mouse cursor is threatening us. Let's run away!
            // It will make the brain start calling runAway() from
            // now on.
            brain.setState(runAway);
        }
    }

    /**
    * The "goHome" state.
    * It makes the ant move towards its home.
    */
    public function goHome() :void {
        // Move the ant towards home
        velocity = new Vector3D(Game.instance.home.x - position.x, Game.instance.home.y - position.y);

        if (distance(Game.instance.home, this) <= 10) {
            // The ant is home, let's find the leaf again.
            brain.setState(findLeaf);
        }
    }

    /**
    * The "runAway" state.
    * It makes the ant run away from the mouse cursor.
    */
    public function runAway() :void {
        // Move the ant away from the mouse cursor
        velocity = new Vector3D(position.x - Game.mouse.x, position.y - Game.mouse.y);

        // Is the mouse cursor still close?
        if (distance(Game.mouse, this) > MOUSE_THREAT_RADIUS) {
            // No, the mouse cursor has gone away. Let's go back looking for the leaf.
            brain.setState(findLeaf);
        }
    }

    public function update():void {
        // Update the FSM controlling the "brain". It will invoke the currently
        // active state function: findLeaf(), goHome() or runAway().
        brain.update();

        // Apply the velocity vector to the position, making the ant move.
        moveBasedOnVelocity();
    }

    (...)
}

1
हालांकि यह लिंक प्रश्न का उत्तर दे सकता है, लेकिन उत्तर के आवश्यक भागों को शामिल करना और संदर्भ के लिए लिंक प्रदान करना बेहतर है। लिंक-केवल उत्तर अमान्य हो सकते हैं यदि लिंक किए गए पृष्ठ बदल जाते हैं। - समीक्षा से
drneel

@drneel मैं ट्यूटोरियल से बिट्स कॉपी और पेस्ट कर सकता हूं ... लेकिन क्या यह लेखक से क्रेडिट नहीं लेगा?
जेट ब्लू

1
@JetBlue: उत्तर को लिंक को संदर्भ के रूप में छोड़ दें, और संबंधित शब्दों को उत्तर पोस्ट में अपने शब्दों में शामिल करें ताकि किसी के कॉपीराइट को न तोड़े। मुझे पता है कि यह सख्त लगता है, लेकिन इस नियम के कारण बहुत सारे उत्तर बहुत ज्यादा बेहतर हो गए हैं।
फ़्लिम

6

आज मैं स्टेट डिजाइन पैटर्न में गहरी हूं। मैंने थ्रेडस्टेट का परीक्षण और परीक्षण किया, जो C # में थ्रेडिंग के बराबर (+/-) है, जैसा कि थ्रेडिंग से C # में चित्र में वर्णित है।

यहां छवि विवरण दर्ज करें

आप आसानी से नए राज्यों को जोड़ सकते हैं, एक राज्य से दूसरे राज्य में चालें कॉन्फ़िगर करना बहुत आसान है क्योंकि यह राज्य के कार्यान्वयन में बिगड़ा हुआ है

कार्यान्वयन और उपयोग में: राज्य डिजाइन पैटर्न द्वारा लागू .NET थ्रेडस्टैट


1
लिंक मर चुका है। क्या आपके पास एक और है?
रोल

5

मैंने C # में अभी तक FSM को लागू करने की कोशिश नहीं की है, लेकिन ये सभी ध्वनि (या देखो) बहुत जटिल हैं जिस तरह से मैंने एफएसएम को अतीत में सी या एएसएम जैसी निम्न-स्तरीय भाषाओं में संभाला है।

मेरा मानना ​​है कि जिस विधि को मैंने हमेशा जाना है उसे "Iterative लूप" की तरह कहा जाता है। इसमें, आपके पास अनिवार्य रूप से एक 'जबकि ’लूप होता है जो समय-समय पर घटनाओं (व्यवधान) के आधार पर बाहर निकलता है, फिर मुख्य लूप में वापस आ जाता है।

व्यवधान संचालकों के भीतर, आप एक CurrentState पास करेंगे और एक NextState लौटाएंगे, जो तब मुख्य पाश में CurrentState चर को अधिलेखित कर देगा। जब तक प्रोग्राम बंद नहीं हो जाता (या माइक्रोकंट्रोलर रीसेट करता है) आप इस विज्ञापन का उल्लंघन करते हैं।

मैं अन्य उत्तरों को देख रहा हूं, सभी एफएसएम के साथ तुलना में बहुत जटिल लगते हैं, मेरे दिमाग में इसे लागू करने का इरादा है; इसकी सुंदरता इसकी सादगी में निहित है और एफएसएम कई, कई राज्यों और संक्रमणों के साथ बहुत जटिल हो सकता है, लेकिन वे जटिल प्रक्रिया को आसानी से टूटने और पचाने की अनुमति देते हैं।

मुझे लगता है कि मेरी प्रतिक्रिया में एक और प्रश्न शामिल नहीं होना चाहिए, लेकिन मुझे यह पूछने के लिए मजबूर होना चाहिए: ये अन्य प्रस्तावित समाधान इतने जटिल क्यों दिखाई देते हैं?
वे एक विशाल कील स्लेज हथौड़ा के साथ एक छोटी सी कील को मारने के समान प्रतीत होते हैं।


1
पूरी तरह से सहमत हूँ। स्विच स्टेटमेंट के साथ एक सरल जबकि लूप उतना ही सरल है जितना आप प्राप्त कर सकते हैं।
रोल

2
जब तक आपके पास कई राज्यों और स्थितियों के साथ एक बहुत जटिल राज्य मशीन नहीं है, जहां आप कई नेस्टेड स्विच के साथ समाप्त हो जाएंगे। आपके लूप कार्यान्वयन के आधार पर, व्यस्त-प्रतीक्षा में भी जुर्माना हो सकता है।
सून रिवर्स

3

क्या एक बाउट स्टेटपैटर्न। क्या यह आपकी आवश्यकताओं के अनुरूप है?

मुझे लगता है कि इसका संदर्भ संबंधित है, लेकिन इसका एक शॉट यकीन के लिए है।

http://en.wikipedia.org/wiki/State_pattern

इससे आपके राज्य तय करते हैं कि कहां जाना है और "ऑब्जेक्ट" क्लास नहीं।

ब्रूनो


1
राज्य का पैटर्न एक ऐसे वर्ग से संबंधित है जो उस स्थिति / मोड के आधार पर अलग-अलग कार्य कर सकता है, जो राज्यों के बीच संक्रमण से नहीं निपटता है।
एली अलग्रांति

3

मेरी राय में एक राज्य मशीन न केवल बदलते राज्यों के लिए है, बल्कि एक विशेष राज्य के भीतर ट्रिगर्स / घटनाओं को संभालने के लिए भी (बहुत महत्वपूर्ण) है। यदि आप राज्य मशीन डिजाइन पैटर्न को बेहतर ढंग से समझना चाहते हैं, तो पुस्तक के पहले डिजाइन पैटर्न पेज 320 में एक अच्छा विवरण पाया जा सकता है ।

यह न केवल चर के भीतर राज्यों के बारे में है, बल्कि विभिन्न राज्यों के भीतर ट्रिगर को संभालने के बारे में भी है। महान अध्याय (और नहीं, इस :-) का उल्लेख करने में मेरे लिए कोई शुल्क नहीं है जिसमें स्पष्टीकरण को समझने के लिए बस एक आसान है।


3

मैंने अभी यह योगदान दिया है:

https://code.google.com/p/ysharp/source/browse/#svn%2Ftrunk%2FStateMachinesPoC

IObserver (संकेत के) के रूप में राज्यों के साथ, प्रत्यक्ष और अप्रत्यक्ष रूप से आदेशों को प्रदर्शित करने वाले उदाहरणों में से एक है, इस प्रकार एक संकेत स्रोत के उत्तरदाता, IObservable (संकेत के):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSampleAdvanced
    {
        // Enum type for the transition triggers (instead of System.String) :
        public enum TvOperation { Plug, SwitchOn, SwitchOff, Unplug, Dispose }

        // The state machine class type is also used as the type for its possible states constants :
        public class Television : NamedState<Television, TvOperation, DateTime>
        {
            // Declare all the possible states constants :
            public static readonly Television Unplugged = new Television("(Unplugged TV)");
            public static readonly Television Off = new Television("(TV Off)");
            public static readonly Television On = new Television("(TV On)");
            public static readonly Television Disposed = new Television("(Disposed TV)");

            // For convenience, enter the default start state when the parameterless constructor executes :
            public Television() : this(Television.Unplugged) { }

            // To create a state machine instance, with a given start state :
            private Television(Television value) : this(null, value) { }

            // To create a possible state constant :
            private Television(string moniker) : this(moniker, null) { }

            private Television(string moniker, Television value)
            {
                if (moniker == null)
                {
                    // Build the state graph programmatically
                    // (instead of declaratively via custom attributes) :
                    Handler<Television, TvOperation, DateTime> stateChangeHandler = StateChange;
                    Build
                    (
                        new[]
                        {
                            new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.Unplugged, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.SwitchOn, Goto = Television.On, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.Off, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.SwitchOff, Goto = Television.Off, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Unplug, Goto = Television.Unplugged, With = stateChangeHandler },
                            new { From = Television.On, When = TvOperation.Dispose, Goto = Television.Disposed, With = stateChangeHandler }
                        },
                        false
                    );
                }
                else
                    // Name the state constant :
                    Moniker = moniker;
                Start(value ?? this);
            }

            // Because the states' value domain is a reference type, disallow the null value for any start state value : 
            protected override void OnStart(Television value)
            {
                if (value == null)
                    throw new ArgumentNullException("value", "cannot be null");
            }

            // When reaching a final state, unsubscribe from all the signal source(s), if any :
            protected override void OnComplete(bool stateComplete)
            {
                // Holds during all transitions into a final state
                // (i.e., stateComplete implies IsFinal) :
                System.Diagnostics.Debug.Assert(!stateComplete || IsFinal);

                if (stateComplete)
                    UnsubscribeFromAll();
            }

            // Executed before and after every state transition :
            private void StateChange(IState<Television> state, ExecutionStep step, Television value, TvOperation info, DateTime args)
            {
                // Holds during all possible transitions defined in the state graph
                // (i.e., (step equals ExecutionStep.LeaveState) implies (not state.IsFinal))
                System.Diagnostics.Debug.Assert((step != ExecutionStep.LeaveState) || !state.IsFinal);

                // Holds in instance (i.e., non-static) transition handlers like this one :
                System.Diagnostics.Debug.Assert(this == state);

                switch (step)
                {
                    case ExecutionStep.LeaveState:
                        var timeStamp = ((args != default(DateTime)) ? String.Format("\t\t(@ {0})", args) : String.Empty);
                        Console.WriteLine();
                        // 'value' is the state value that we are transitioning TO :
                        Console.WriteLine("\tLeave :\t{0} -- {1} -> {2}{3}", this, info, value, timeStamp);
                        break;
                    case ExecutionStep.EnterState:
                        // 'value' is the state value that we have transitioned FROM :
                        Console.WriteLine("\tEnter :\t{0} -- {1} -> {2}", value, info, this);
                        break;
                    default:
                        break;
                }
            }

            public override string ToString() { return (IsConstant ? Moniker : Value.ToString()); }
        }

        public static void Run()
        {
            Console.Clear();

            // Create a signal source instance (here, a.k.a. "remote control") that implements
            // IObservable<TvOperation> and IObservable<KeyValuePair<TvOperation, DateTime>> :
            var remote = new SignalSource<TvOperation, DateTime>();

            // Create a television state machine instance (automatically set in a default start state),
            // and make it subscribe to a compatible signal source, such as the remote control, precisely :
            var tv = new Television().Using(remote);
            bool done;

            // Always holds, assuming the call to Using(...) didn't throw an exception (in case of subscription failure) :
            System.Diagnostics.Debug.Assert(tv != null, "There's a bug somewhere: this message should never be displayed!");

            // As commonly done, we can trigger a transition directly on the state machine :
            tv.MoveNext(TvOperation.Plug, DateTime.Now);

            // Alternatively, we can also trigger transitions by emitting from the signal source / remote control
            // that the state machine subscribed to / is an observer of :
            remote.Emit(TvOperation.SwitchOn, DateTime.Now);
            remote.Emit(TvOperation.SwitchOff);
            remote.Emit(TvOperation.SwitchOn);
            remote.Emit(TvOperation.SwitchOff, DateTime.Now);

            done =
                (
                    tv.
                        MoveNext(TvOperation.Unplug).
                        MoveNext(TvOperation.Dispose) // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            remote.Emit(TvOperation.Unplug); // Ignored by the state machine thanks to the OnComplete(...) override above

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

नोट: यह उदाहरण बल्कि कृत्रिम है और इसका अर्थ है कई ऑर्थोगोनल विशेषताओं का प्रदर्शन करना। इस तरह से CRTP (देखें: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) का उपयोग करके, शायद ही कभी एक पूर्ण विकसित वर्ग द्वारा राज्य मूल्य डोमेन को लागू करने की वास्तविक आवश्यकता होनी चाहिए ।

यहाँ एक समान राज्य मशीन के लिए, निश्चित रूप से सरल और संभवतः अधिक सामान्य कार्यान्वयन उपयोग के मामले (राज्यों के मूल्य डोमेन के रूप में एक सरल एनम प्रकार का उपयोग करके) और उसी परीक्षण मामले के साथ है:

https://code.google.com/p/ysharp/source/browse/trunk/StateMachinesPoC/WatchingTVSample.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Test
{
    using Machines;

    public static class WatchingTvSample
    {
        public enum Status { Unplugged, Off, On, Disposed }

        public class DeviceTransitionAttribute : TransitionAttribute
        {
            public Status From { get; set; }
            public string When { get; set; }
            public Status Goto { get; set; }
            public object With { get; set; }
        }

        // State<Status> is a shortcut for / derived from State<Status, string>,
        // which in turn is a shortcut for / derived from State<Status, string, object> :
        public class Device : State<Status>
        {
            // Executed before and after every state transition :
            protected override void OnChange(ExecutionStep step, Status value, string info, object args)
            {
                if (step == ExecutionStep.EnterState)
                {
                    // 'value' is the state value that we have transitioned FROM :
                    Console.WriteLine("\t{0} -- {1} -> {2}", value, info, this);
                }
            }

            public override string ToString() { return Value.ToString(); }
        }

        // Since 'Device' has no state graph of its own, define one for derived 'Television' :
        [DeviceTransition(From = Status.Unplugged, When = "Plug", Goto = Status.Off)]
        [DeviceTransition(From = Status.Unplugged, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.Off, When = "Switch On", Goto = Status.On)]
        [DeviceTransition(From = Status.Off, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.Off, When = "Dispose", Goto = Status.Disposed)]
        [DeviceTransition(From = Status.On, When = "Switch Off", Goto = Status.Off)]
        [DeviceTransition(From = Status.On, When = "Unplug", Goto = Status.Unplugged)]
        [DeviceTransition(From = Status.On, When = "Dispose", Goto = Status.Disposed)]
        public class Television : Device { }

        public static void Run()
        {
            Console.Clear();

            // Create a television state machine instance, and return it, set in some start state :
            var tv = new Television().Start(Status.Unplugged);
            bool done;

            // Holds iff the chosen start state isn't a final state :
            System.Diagnostics.Debug.Assert(tv != null, "The chosen start state is a final state!");

            // Trigger some state transitions with no arguments
            // ('args' is ignored by this state machine's OnChange(...), anyway) :
            done =
                (
                    tv.
                        MoveNext("Plug").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Switch On").
                        MoveNext("Switch Off").
                        MoveNext("Unplug").
                        MoveNext("Dispose") // MoveNext(...) returns null iff tv.IsFinal == true
                    == null
                );

            Console.WriteLine();
            Console.WriteLine("Is the TV's state '{0}' a final state? {1}", tv.Value, done);

            Console.WriteLine();
            Console.WriteLine("Press any key...");
            Console.ReadKey();
        }
    }
}

'HTH


क्या यह थोड़ा अजीब नहीं है कि प्रत्येक राज्य के उदाहरण में राज्य ग्राफ की अपनी प्रति है?
ग्रू

@ ग्रू: नहीं, वे नहीं। केवल टेलीविजन के उदाहरणों में मोनिकरर के लिए एक नल स्ट्रिंग के साथ निजी कंस्ट्रक्टर का उपयोग करके बनाया गया है (इसलिए, संरक्षित 'बिल्ड' विधि को कॉल करना) राज्य ग्राफ होगा, राज्य मशीनों के रूप में। अन्य, जिनके नाम टेलीविज़न हैं (एक मणिकर के साथ जो उस पारंपरिक और तदर्थ उद्देश्य के लिए अशक्त नहीं है ) केवल "फिक्स पॉइंट" स्टेट्स (इसलिए बोलने के लिए) होंगे, जो राज्य स्थिरांक के रूप में सेवा कर रहे हैं (जो कि राज्य का ग्राफ है) वास्तविक राज्य मशीनों को उनके कोने के रूप में संदर्भित किया जाएगा)। 'HTH,
YSharp

ठीक है, मैं इसे लेता हूं। वैसे भी, IMHO, यह बेहतर होता अगर आपने कुछ कोड शामिल किए होते जो वास्तव में इन बदलावों को संभालते। इस तरह, यह केवल आपके पुस्तकालय के लिए एक (IMHO) नहीं-तो-स्पष्ट इंटरफ़ेस का उपयोग करने के उदाहरण के रूप में कार्य करता है। उदाहरण के लिए, कैसे StateChangeहल किया जाता है? प्रतिबिंब के माध्यम से? क्या यह सचमुच आवश्यक है?
Groo

1
@Groo: अच्छी टिप्पणी है। यह वास्तव में उस पहले उदाहरण में हैंडलर पर प्रतिबिंबित करने के लिए आवश्यक नहीं है क्योंकि यह वहां प्रोग्रामेटिक रूप से सटीक रूप से किया जाता है और इसे सांख्यिकीय रूप से बाध्य किया जा सकता है / टाइप किया जा सकता है (कस्टम विशेषताओं के माध्यम से विपरीत)। इसलिए यह काम भी अपेक्षित है: private Television(string moniker, Television value) { Handler<Television, TvOperation, DateTime> myHandler = StateChange; // (code omitted) new { From = Television.Unplugged, When = TvOperation.Plug, Goto = Television.Off, With = myHandler } }
YSharp

1
आपके प्रयास के लिए धन्यवाद!
Groo

3

मैंने यह जेनेरिक स्टेट मशीन बनाई को जूलियट के कोड से बाहर कर दिया। यह मेरे लिए कमाल का काम कर रहा है।

ये हैं फायदे:

  • आप दो enums के साथ कोड में नई राज्य मशीन बना सकते हैं TStateऔरTCommand ,
  • TransitionResult<TState>के आउटपुट परिणामों पर अधिक नियंत्रण रखने के लिए संरचना को जोड़ा[Try]GetNext()विधियों
  • नेस्टेड वर्ग को उजागर StateTransition केवल के माध्यम से AddTransition(TState, TCommand, TState)इसके साथ काम करने के लिए यह आसान बनाने के

कोड:

public class StateMachine<TState, TCommand>
    where TState : struct, IConvertible, IComparable
    where TCommand : struct, IConvertible, IComparable
{
    protected class StateTransition<TS, TC>
        where TS : struct, IConvertible, IComparable
        where TC : struct, IConvertible, IComparable
    {
        readonly TS CurrentState;
        readonly TC Command;

        public StateTransition(TS currentState, TC command)
        {
            if (!typeof(TS).IsEnum || !typeof(TC).IsEnum)
            {
                throw new ArgumentException("TS,TC must be an enumerated type");
            }

            CurrentState = currentState;
            Command = command;
        }

        public override int GetHashCode()
        {
            return 17 + 31 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
        }

        public override bool Equals(object obj)
        {
            StateTransition<TS, TC> other = obj as StateTransition<TS, TC>;
            return other != null
                && this.CurrentState.CompareTo(other.CurrentState) == 0
                && this.Command.CompareTo(other.Command) == 0;
        }
    }

    private Dictionary<StateTransition<TState, TCommand>, TState> transitions;
    public TState CurrentState { get; private set; }

    protected StateMachine(TState initialState)
    {
        if (!typeof(TState).IsEnum || !typeof(TCommand).IsEnum)
        {
            throw new ArgumentException("TState,TCommand must be an enumerated type");
        }

        CurrentState = initialState;
        transitions = new Dictionary<StateTransition<TState, TCommand>, TState>();
    }

    /// <summary>
    /// Defines a new transition inside this state machine
    /// </summary>
    /// <param name="start">source state</param>
    /// <param name="command">transition condition</param>
    /// <param name="end">destination state</param>
    protected void AddTransition(TState start, TCommand command, TState end)
    {
        transitions.Add(new StateTransition<TState, TCommand>(start, command), end);
    }

    public TransitionResult<TState> TryGetNext(TCommand command)
    {
        StateTransition<TState, TCommand> transition = new StateTransition<TState, TCommand>(CurrentState, command);
        TState nextState;
        if (transitions.TryGetValue(transition, out nextState))
            return new TransitionResult<TState>(nextState, true);
        else
            return new TransitionResult<TState>(CurrentState, false);
    }

    public TransitionResult<TState> MoveNext(TCommand command)
    {
        var result = TryGetNext(command);
        if(result.IsValid)
        {
            //changes state
            CurrentState = result.NewState;
        }
        return result;
    }
}

यह TryGetNext विधि का रिटर्न प्रकार है:

public struct TransitionResult<TState>
{
    public TransitionResult(TState newState, bool isValid)
    {
        NewState = newState;
        IsValid = isValid;
    }
    public TState NewState;
    public bool IsValid;
}

कैसे इस्तेमाल करे:

यह आप कैसे बना सकते हैं OnlineDiscountStateMachine सामान्य वर्ग से :

OnlineDiscountStateअपने राज्यों और एक Enum के लिए एक Enum परिभाषित करेंOnlineDiscountCommand लिए और उसके आदेशों के लिए ।

एक वर्ग को परिभाषित करें OnlineDiscountStateMachineउन दो एनमों का उपयोग करके जेनेरिक वर्ग से प्राप्त

कंस्ट्रक्टर को base(OnlineDiscountState.InitialState)ऐसा बनाएं जिससे प्रारंभिक अवस्था सेट होOnlineDiscountState.InitialState

AddTransitionआवश्यकतानुसार कई बार उपयोग करें

public class OnlineDiscountStateMachine : StateMachine<OnlineDiscountState, OnlineDiscountCommand>
{
    public OnlineDiscountStateMachine() : base(OnlineDiscountState.Disconnected)
    {
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Connected);
        AddTransition(OnlineDiscountState.Disconnected, OnlineDiscountCommand.Connect, OnlineDiscountState.Error_AuthenticationError);
        AddTransition(OnlineDiscountState.Connected, OnlineDiscountCommand.Submit, OnlineDiscountState.WaitingForResponse);
        AddTransition(OnlineDiscountState.WaitingForResponse, OnlineDiscountCommand.DataReceived, OnlineDiscountState.Disconnected);
    }
}

व्युत्पन्न राज्य मशीन का उपयोग करें

    odsm = new OnlineDiscountStateMachine();
    public void Connect()
    {
        var result = odsm.TryGetNext(OnlineDiscountCommand.Connect);

        //is result valid?
        if (!result.IsValid)
            //if this happens you need to add transitions to the state machine
            //in this case result.NewState is the same as before
            Console.WriteLine("cannot navigate from this state using OnlineDiscountCommand.Connect");

        //the transition was successfull
        //show messages for new states
        else if(result.NewState == OnlineDiscountState.Error_AuthenticationError)
            Console.WriteLine("invalid user/pass");
        else if(result.NewState == OnlineDiscountState.Connected)
            Console.WriteLine("Connected");
        else
            Console.WriteLine("not implemented transition result for " + result.NewState);
    }

1

मुझे लगता है कि जूलियट द्वारा प्रस्तावित राज्य मशीन में एक गलती है: विधि GetHashCode दो अलग-अलग संक्रमणों के लिए एक ही हैश कोड वापस कर सकता है, उदाहरण के लिए:

राज्य = सक्रिय (1), कमांड = ठहराव (2) => हैशकोड = 17 + 31 + 62 = 110

राज्य = रुका हुआ (२), आज्ञा = अंत (१) => हैशकोड = १ 62 + ६२ + ३१ = ११०

इस त्रुटि से बचने के लिए, विधि इस प्रकार होनी चाहिए:

public override int GetHashCode()
   {
            return 17 + 23 * CurrentState.GetHashCode() + 31 * Command.GetHashCode();
   }

एलेक्स


1
किसी भी संभावित संयोजन के लिए एक अद्वितीय संख्या वापस करने के लिए हैश कोड की आवश्यकता नहीं होती है, केवल लक्ष्य श्रेणी में अच्छे वितरण के साथ एक अलग मूल्य (इस मामले में सीमा सभी संभव intमान हैं)। इसीलिए HashCodeहमेशा साथ दिया जाता है Equals। यदि हैश कोड समान हैं, तो Equalsविधि का उपयोग करके ऑब्जेक्ट को सटीक eaaulity के लिए जांचा जाता है ।
दिमित्री एवोन्टोमोव

0

FiniteStateMachine एक साधारण स्टेट मशीन है, जिसे C # लिंक में लिखा गया है

मेरी लाइब्रेरी FiniteStateMachine का उपयोग करें

  1. बाहरी दुनिया के लिए एकल इंटरफ़ेस प्रस्तुत करने के लिए "संदर्भ" वर्ग को परिभाषित करें।
  2. एक राज्य सार आधार वर्ग को परिभाषित करें।
  3. राज्य मशीन के व्युत्पन्न वर्गों के रूप में राज्य मशीन के विभिन्न "राज्यों" का प्रतिनिधित्व करते हैं।
  4. उपयुक्त राज्य व्युत्पन्न वर्गों में राज्य-विशिष्ट व्यवहार को परिभाषित करें।
  5. "संदर्भ" वर्ग में वर्तमान "स्थिति" के लिए एक संकेतक बनाए रखें।
  6. राज्य मशीन की स्थिति को बदलने के लिए, वर्तमान "राज्य" सूचक को बदलें।

DLL डाउनलोड करें

LINQPad पर उदाहरण:

void Main()
{
            var machine = new SFM.Machine(new StatePaused());
            var output = machine.Command("Input_Start", Command.Start);
            Console.WriteLine(Command.Start.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);

            output = machine.Command("Input_Pause", Command.Pause);
            Console.WriteLine(Command.Pause.ToString() + "->  State: " + machine.Current);
            Console.WriteLine(output);
            Console.WriteLine("-------------------------------------------------");
}
    public enum Command
    {
        Start,
        Pause,
    }

    public class StateActive : SFM.State
    {

        public override void Handle(SFM.IContext context)

        {
            //Gestione parametri
            var input = (String)context.Input;
            context.Output = input;

            //Gestione Navigazione
            if ((Command)context.Command == Command.Pause) context.Next = new StatePaused();
            if ((Command)context.Command == Command.Start) context.Next = this;

        }
    }


public class StatePaused : SFM.State
{

     public override void Handle(SFM.IContext context)

     {

         //Gestione parametri
         var input = (String)context.Input;
         context.Output = input;

         //Gestione Navigazione
         if ((Command)context.Command == Command.Start) context.Next = new  StateActive();
         if ((Command)context.Command == Command.Pause) context.Next = this;


     }

 }

1
इसके पास GNU GPL लाइसेंस है।
डेर_मिस्टर

0

मैं State.cs की सिफारिश करूंगा । मैंने व्यक्तिगत रूप से State.js (जावास्क्रिप्ट संस्करण) का इस्तेमाल किया और इससे बहुत खुश हूं। यह C # संस्करण समान तरीके से काम करता है।

आप त्वरित स्थिति:

        // create the state machine
        var player = new StateMachine<State>( "player" );

        // create some states
        var initial = player.CreatePseudoState( "initial", PseudoStateKind.Initial );
        var operational = player.CreateCompositeState( "operational" );
        ...

आप कुछ बदलाव तुरंत करते हैं:

        var t0 = player.CreateTransition( initial, operational );
        player.CreateTransition( history, stopped );
        player.CreateTransition<String>( stopped, running, ( state, command ) => command.Equals( "play" ) );
        player.CreateTransition<String>( active, stopped, ( state, command ) => command.Equals( "stop" ) );

आप राज्यों और बदलावों पर कार्रवाई परिभाषित करते हैं:

    t0.Effect += DisengageHead;
    t0.Effect += StopMotor;

और बस यही सब है। अधिक जानकारी के लिए वेबसाइट देखें।


0

NuGet में 2 लोकप्रिय स्टेट मशीन पैकेज हैं।

Appccelerate.StateMachine ( 13.6K डाउनलोड + 3.82K विरासत संस्करण (bbv.Common.StateMachine)

StateMachineToolkit (1.56K डाउनलोड)

Appccelerate lib में अच्छे प्रलेखन हैं , लेकिन यह .NET 4 का समर्थन नहीं करता है, इसलिए मैंने अपने प्रोजेक्ट के लिए StateMachineToolkit को चुना।


0

इस रेपो में अन्य विकल्प https://github.com/lingkodsoft/StateBliss धाराप्रवाह वाक्य रचना का उपयोग करता है, ट्रिगर का समर्थन करता है।

    public class BasicTests
    {
        [Fact]
        public void Tests()
        {
            // Arrange
            StateMachineManager.Register(new [] { typeof(BasicTests).Assembly }); //Register at bootstrap of your application, i.e. Startup
            var currentState = AuthenticationState.Unauthenticated;
            var nextState = AuthenticationState.Authenticated;
            var data = new Dictionary<string, object>();

            // Act
            var changeInfo = StateMachineManager.Trigger(currentState, nextState, data);

            // Assert
            Assert.True(changeInfo.StateChangedSucceeded);
            Assert.Equal("ChangingHandler1", changeInfo.Data["key1"]);
            Assert.Equal("ChangingHandler2", changeInfo.Data["key2"]);
        }

        //this class gets regitered automatically by calling StateMachineManager.Register
        public class AuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler1)
                    .Changed(this, a => a.ChangedHandler1);

                builder.OnEntering(AuthenticationState.Authenticated, this, a => a.OnEnteringHandler1);
                builder.OnEntered(AuthenticationState.Authenticated, this, a => a.OnEnteredHandler1);

                builder.OnExiting(AuthenticationState.Unauthenticated, this, a => a.OnExitingHandler1);
                builder.OnExited(AuthenticationState.Authenticated, this, a => a.OnExitedHandler1);

                builder.OnEditing(AuthenticationState.Authenticated, this, a => a.OnEditingHandler1);
                builder.OnEdited(AuthenticationState.Authenticated, this, a => a.OnEditedHandler1);

                builder.ThrowExceptionWhenDiscontinued = true;
            }

            private void ChangingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key1"] = "ChangingHandler1";
            }

            private void OnEnteringHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                // changeinfo.Continue = false; //this will prevent changing the state
            }

            private void OnEditedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnExitedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEnteredHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {                
            }

            private void OnEditingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void OnExitingHandler1(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
            }

            private void ChangedHandler1(StateChangeInfo<AuthenticationState> changeinfo)
            {
            }
        }

        public class AnotherAuthenticationStateDefinition : StateDefinition<AuthenticationState>
        {
            public override void Define(IStateFromBuilder<AuthenticationState> builder)
            {
                builder.From(AuthenticationState.Unauthenticated).To(AuthenticationState.Authenticated)
                    .Changing(this, a => a.ChangingHandler2);

            }

            private void ChangingHandler2(StateChangeGuardInfo<AuthenticationState> changeinfo)
            {
                var data = changeinfo.DataAs<Dictionary<string, object>>();
                data["key2"] = "ChangingHandler2";
            }
        }
    }

    public enum AuthenticationState
    {
        Unauthenticated,
        Authenticated
    }
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.