Contoh mesin keadaan sederhana di C #?

258

Memperbarui:

Sekali lagi terima kasih atas contohnya, mereka sangat membantu dan dengan yang berikut ini saya tidak bermaksud mengambil apa pun dari mereka.

Bukankah contoh yang diberikan saat ini, sejauh yang saya mengerti mereka & mesin negara, hanya setengah dari apa yang biasanya kita mengerti oleh mesin negara?
Dalam arti bahwa contoh tidak mengubah keadaan tapi itu hanya diwakili oleh mengubah nilai variabel (dan memungkinkan nilai yang berbeda - perubahan di berbagai negara), sementara biasanya mesin negara juga harus mengubah perilaku itu, dan perilaku tidak (hanya) di arti memungkinkan perubahan nilai yang berbeda untuk variabel tergantung pada negara, tetapi dalam arti memungkinkan metode yang berbeda dieksekusi untuk negara yang berbeda.

Atau apakah saya memiliki kesalahpahaman tentang mesin negara dan penggunaan umum mereka?

salam Hormat


Pertanyaan asli:

Saya menemukan diskusi ini tentang mesin negara & blok iterator di c # dan alat untuk membuat mesin negara dan apa yang tidak untuk C #, jadi saya menemukan banyak hal abstrak tetapi sebagai noob semua ini agak membingungkan.

Jadi akan lebih baik jika seseorang dapat memberikan contoh kode sumber C # yang mewujudkan mesin keadaan sederhana dengan mungkin 3,4 negara, hanya untuk mendapatkan intinya.


Jennifer Owens
sumber
Apakah Anda bertanya-tanya tentang mesin negara secara umum atau hanya yang berbasis iterator?
Skurmedel
2
Ada. Net Core Stateless lib dengan contoh, DAGs daigram dll. - layak untuk diulas
zmische

Jawaban:

416

Mari kita mulai dengan diagram keadaan sederhana ini:

diagram mesin keadaan sederhana

Kita punya:

  • 4 status (Tidak Aktif, Aktif, Dijeda, dan Keluar)
  • 5 jenis transisi negara (Mulai Perintah, Akhiri Perintah, Jeda Perintah, Lanjutkan Perintah, Keluar Perintah).

Anda dapat mengonversikan ini ke C # dalam beberapa cara, seperti melakukan pernyataan beralih pada status dan perintah saat ini, atau mencari transisi dalam tabel transisi. Untuk mesin keadaan sederhana ini, saya lebih suka tabel transisi, yang sangat mudah direpresentasikan menggunakan 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();
        }
    }
}

Sebagai pilihan pribadi, saya suka mendesain mesin negara saya dengan GetNextfungsi untuk mengembalikan negara berikutnya secara deterministik , dan MoveNextfungsi untuk bermutasi mesin negara.

Juliet
sumber
66
+1 untuk implementasi GetHashCode()penggunaan bilangan prima yang benar.
ja72
13
Bisakah Anda jelaskan tujuan dari GetHashCode ()?
Siddharth
14
@Siddharth: StateTransitionKelas digunakan sebagai kunci dalam kamus dan persamaan kunci adalah penting. Dua contoh berbeda dari StateTransitionharus dianggap sama selama mereka mewakili transisi yang sama (misalnya CurrentStatedan Commandsama). Untuk menerapkan kesetaraan, Anda harus menimpa Equalsjuga GetHashCode. Khususnya kamus akan menggunakan kode hash dan dua objek yang sama harus mengembalikan kode hash yang sama. Anda juga mendapatkan kinerja yang baik jika tidak terlalu banyak objek yang tidak sama memiliki kode hash yang sama, itulah sebabnya GetHashCodediimplementasikan seperti yang ditunjukkan.
Martin Liversage
14
Meskipun ini pasti membuat Anda mesin negara (dan implementasi ish C # 'yang tepat juga), saya merasa masih ada jawaban untuk pertanyaan OP tentang perubahan perilaku? Setelah semua, itu hanya menghitung negara tetapi perilaku yang terkait dengan perubahan negara, daging sebenarnya dari program dan biasanya disebut peristiwa Masuk / Keluar, masih hilang.
stijn
2
Jika seseorang membutuhkannya: Saya menyesuaikan mesin tate ini dan menggunakannya dalam game unity saya. Ini tersedia di hub git: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89
73

Anda mungkin ingin menggunakan salah satu Mesin Finite State open source yang ada. Misalnya bbv.Common.StateMachine ditemukan di http://code.google.com/p/bbvcommon/wiki/StateMachine . Ini memiliki sintaks fasih yang sangat intuitif dan banyak fitur seperti, tindakan masuk / keluar, tindakan transisi, penjaga, hirarki, implementasi pasif (dijalankan pada utas penelepon) dan implementasi aktif (memiliki utas di mana fsm berjalan, acara ditambahkan ke antrian).

Contoh Juliet, definisi mesin negara menjadi sangat mudah:

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);

Pembaruan : Lokasi proyek telah pindah ke: https://github.com/appccelerate/statemachine

Remo Gloor
sumber
4
Terima kasih telah mereferensikan mesin status open source yang luar biasa ini. Bisakah saya bertanya bagaimana saya bisa mendapatkan kondisi saat ini?
Ramazan Polat
3
Anda tidak bisa dan tidak seharusnya. Negara adalah sesuatu yang tidak stabil. Saat Anda meminta status, ada kemungkinan Anda berada di tengah transisi. Semua tindakan harus dilakukan dalam transisi, entri negara dan keluar negara. Jika Anda benar-benar ingin memiliki status maka Anda dapat menambahkan bidang lokal dan menetapkan status dalam tindakan entri.
Remo Gloor
4
Pertanyaannya adalah untuk apa yang Anda "butuhkan" dan jika Anda benar-benar membutuhkan negara SM atau semacam negara lainnya. Misalnya jika Anda memerlukan beberapa teks tampilan maka beberapa yang dinyatakan dapat memiliki teks tampilan yang sama misalnya jika mempersiapkan untuk pengiriman memiliki beberapa sub negara. Dalam hal ini Anda harus melakukan persis apa yang ingin Anda lakukan. Perbarui beberapa teks tampilan di tempat yang benar. Misalnya dalam ExecuteOnEntry. Jika Anda memerlukan info lebih lanjut, ajukan Pertanyaan baru dan sebutkan persis masalah Anda karena topik ini tidak dibahas di sini.
Remo Gloor
Ok, saya mengajukan pertanyaan baru dan menunggu Anda untuk membalas. Karena saya tidak berpikir orang lain menyelesaikan masalah ini karena Anda memiliki jawaban terbaik tetapi tetap saja penanya tidak menerimanya. Saya akan memposting url pertanyaan di sini. Terima kasih.
Ramazan Polat
4
+1 untuk API lancar dan deklaratif. Itu mengagumkan. BTW, kode google sepertinya sudah ketinggalan zaman. Situs proyek terbaru mereka ada di GitHub di sini
KFL
52

Berikut adalah contoh dari mesin keadaan terbatas yang sangat klasik, pemodelan perangkat elektronik yang sangat sederhana (seperti TV)

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; }
    }
}
}
Pete Stensønes
sumber
6
Bagi siapa pun yang baru mengenal mesin negara, ini adalah contoh pertama yang bagus untuk membuat kaki Anda basah terlebih dahulu.
PositiveGuy
2
Saya baru mengenal mesin negara dan serius, ini telah membawakan saya Cahaya - terima kasih!
MC5
1
Saya menyukai implementasi ini. Bagi siapa saja yang mungkin tersandung pada ini, sedikit "perbaikan". Di kelas FSM, saya menambahkan private void DoNothing() {return;}dan mengganti semua instance dari null dengan this.DoNothing. Memiliki efek samping yang menyenangkan mengembalikan keadaan saat ini.
Sethmo011
1
Saya bertanya-tanya apakah ada alasan di balik beberapa nama ini. Ketika saya melihat ini, intuisi pertama saya adalah mengganti nama elemen Statesto Unpowered, Standby, On. Alasan saya adalah jika seseorang bertanya kepada saya di negara mana televisi saya berada, saya akan mengatakan "Tidak Aktif" dan bukan "Mulai". Saya juga berubah StandbyWhenOndan StandbyWhenOffke TurnOndan TurnOff. Itu membuat kode dibaca lebih intuitif, tetapi saya bertanya-tanya apakah ada konvensi atau faktor lain yang membuat terminologi saya kurang tepat.
Jason Hamje
Tampaknya masuk akal, saya tidak benar-benar mengikuti konvensi penamaan negara; nama sebagai masuk akal untuk apa pun yang Anda modelkan.
Pete Stensønes
20

Beberapa promo diri yang tidak tahu malu di sini, tetapi beberapa waktu yang lalu saya membuat perpustakaan bernama YieldMachine yang memungkinkan mesin keadaan kompleksitas terbatas dijelaskan dengan cara yang sangat bersih dan sederhana. Misalnya, pertimbangkan lampu:

mesin negara lampu

Perhatikan bahwa mesin status ini memiliki 2 pemicu dan 3 status. Dalam kode YieldMachine, kami menulis metode tunggal untuk semua perilaku yang terkait dengan keadaan, di mana kami melakukan kekejaman mengerikan menggunakan gotountuk setiap negara. Pemicu menjadi properti atau bidang tipe Action, dihiasi dengan atribut yang disebut Trigger. Saya telah mengomentari kode negara bagian pertama dan transisinya di bawah ini; negara bagian berikutnya mengikuti pola yang sama.

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();
    }
}

Pendek dan bagus, eh!

Mesin negara ini dikendalikan hanya dengan mengirim pemicu ke sana:

var sm = new Lamp();
sm.PressSwitch(); //go on
sm.PressSwitch(); //go off

sm.PressSwitch(); //go on
sm.GotError();    //get error
sm.PressSwitch(); //go off

Hanya untuk memperjelas, saya telah menambahkan beberapa komentar ke status pertama untuk membantu Anda memahami cara menggunakan ini.

    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

        ...

Ini berfungsi karena kompiler C # benar-benar membuat mesin keadaan secara internal untuk setiap metode yang digunakan yield return. Konstruk ini biasanya digunakan untuk membuat urutan data dengan malas, tetapi dalam kasus ini kami sebenarnya tidak tertarik dengan urutan yang dikembalikan (yang semuanya adalah nol), tetapi dalam perilaku negara yang dibuat di bawah tenda.

Kelas StateMachinedasar melakukan beberapa refleksi pada konstruksi untuk menetapkan kode untuk setiap [Trigger]tindakan, yang mengatur Triggeranggota dan menggerakkan mesin negara ke depan.

Tetapi Anda tidak benar-benar perlu memahami internal untuk dapat menggunakannya.

skrebbel
sumber
2
"Goto" hanya mengerikan jika melompat di antara metode. Untungnya, itu tidak diizinkan dalam C #.
Brannon
Poin bagus! Bahkan, saya akan sangat terkesan jika bahasa yang diketik secara statis akan memungkinkan untuk menggunakan gotometode antara.
skrebbel
3
@Rannon: bahasa apa yang memungkinkan gotountuk beralih di antara metode? Saya tidak melihat bagaimana itu bisa berhasil. Tidak, gotoapakah bermasalah karena menghasilkan pemrograman prosedural (ini dengan sendirinya menyulitkan hal-hal baik seperti pengujian unit), mempromosikan pengulangan kode (memperhatikan bagaimana InvalidTriggerperlu dimasukkan untuk setiap negara?) Dan akhirnya membuat aliran program lebih sulit untuk diikuti. Bandingkan ini dengan (kebanyakan) solusi lain di utas ini dan Anda akan melihat bahwa ini adalah satu-satunya di mana seluruh FSM terjadi dalam satu metode tunggal. Itu biasanya cukup untuk menimbulkan kekhawatiran.
Groo
1
@ Geo, GW-BASIC, misalnya. Ini membantu bahwa itu tidak memiliki metode, atau bahkan fungsi. Selain itu, saya kesulitan memahami mengapa Anda menemukan "aliran program lebih sulit untuk diikuti" dalam contoh ini. Ini adalah mesin negara, "pergi ke" keadaan dari yang lain adalah satu-satunya hal yang Anda lakukan. Ini memetakan dengan gotocukup baik.
skrebbel
3
GW-BASIC memungkinkan gotountuk beralih antar fungsi, tetapi tidak mendukung fungsi? :) Anda benar, komentar "sulit untuk diikuti" lebih merupakan gotomasalah umum , memang tidak banyak masalah dalam kasus ini.
Groo
13

Anda bisa membuat kode blok iterator yang memungkinkan Anda mengeksekusi blok kode dengan cara yang diatur. Bagaimana blok kode dihancurkan benar-benar tidak harus sesuai dengan apa pun, itu hanya bagaimana Anda ingin kode itu. Sebagai contoh:

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");
}

Dalam hal ini, saat Anda memanggil CountToTen, belum ada yang benar-benar dijalankan. Apa yang Anda dapatkan secara efektif merupakan generator mesin negara, yang Anda dapat membuat mesin instance negara baru. Anda melakukan ini dengan memanggil GetEnumerator (). IEnumerator yang dihasilkan secara efektif mesin negara yang dapat Anda kendarai dengan memanggil MoveNext (...).

Jadi, dalam contoh ini, pertama kali Anda memanggil MoveNext (...) Anda akan melihat "1" ditulis ke konsol, dan saat berikutnya Anda memanggil MoveNext (...) Anda akan melihat 2, 3, 4, dan kemudian 5, 6, 7 dan kemudian 8, dan kemudian 9, 10. Seperti yang Anda lihat, ini adalah mekanisme yang berguna untuk mengatur bagaimana sesuatu harus terjadi.

Kevin Hsu
sumber
6
Tautan wajib dengan peringatan yang adil
lihat
8

Saya memposting jawaban lain di sini karena ini adalah mesin negara dari perspektif yang berbeda; sangat visual.

Jawaban asli saya adalah kode imperatif klasik. Saya pikir ini cukup visual sebagai kode karena array yang membuat memvisualisasikan mesin negara sederhana. Kelemahannya adalah Anda harus menulis semua ini. Jawaban Remos mengurangi upaya menulis kode boiler-plate tetapi jauh lebih sedikit visual. Ada alternatif ketiga; benar-benar menggambar mesin negara.

Jika Anda menggunakan .NET dan dapat menargetkan versi 4 waktu berjalan, maka Anda memiliki opsi untuk menggunakan aktivitas mesin keadaan kerja dari workflow . Ini pada dasarnya membiarkan Anda menggambar mesin negara (seperti dalam diagram Juliet ) dan memiliki WF run-time menjalankannya untuk Anda.

Lihat artikel MSDN Membangun Mesin Negara dengan Windows Workflow Foundation untuk lebih jelasnya, dan situs CodePlex ini untuk versi terbaru.

Itulah opsi yang selalu saya sukai ketika menargetkan .NET karena mudah dilihat, diubah, dan dijelaskan kepada yang bukan programmer; gambar bernilai ribuan kata seperti yang mereka katakan!

Pete Stensønes
sumber
Saya pikir mesin negara adalah salah satu bagian terbaik dari keseluruhan fondasi alur kerja!
fabsenet
7

Penting untuk diingat bahwa mesin negara adalah abstraksi, dan Anda tidak memerlukan alat khusus untuk membuatnya, namun alat dapat berguna.

Misalnya Anda dapat mewujudkan mesin negara dengan fungsi:

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);
}

Mesin ini akan berburu burung camar dan mencoba memukulnya dengan balon air. Jika meleset, ia akan mencoba menembakkannya hingga menyentuh (bisa dilakukan dengan harapan realistis;)), jika tidak, ia akan menertawakan konsol. Terus berburu sampai keluar dari camar untuk melecehkan.

Setiap fungsi sesuai dengan masing-masing negara; status awal dan akhir (atau terima ) tidak ditampilkan. Mungkin ada lebih banyak status di sana daripada yang dimodelkan oleh fungsi. Sebagai contoh setelah menembakkan balon mesin itu benar-benar dalam keadaan lain daripada sebelumnya, tapi saya memutuskan perbedaan ini tidak praktis untuk dibuat.

Cara yang umum adalah dengan menggunakan kelas untuk mewakili negara, dan kemudian menghubungkannya dengan cara yang berbeda.

Skurmedel
sumber
7

Menemukan tutorial hebat ini online dan itu membantu saya membungkus kepala saya di sekitar mesin negara yang terbatas.

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

Tutorialnya adalah agnostik bahasa, sehingga dapat dengan mudah disesuaikan dengan kebutuhan C # Anda.

Juga, contoh yang digunakan (semut yang mencari makanan) mudah dimengerti.


Dari tutorial:

masukkan deskripsi gambar di sini

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();
    }

    (...)
}
Jet Blue
sumber
1
Meskipun tautan ini dapat menjawab pertanyaan, lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini dan memberikan tautan untuk referensi. Jawaban hanya tautan dapat menjadi tidak valid jika halaman tertaut berubah. - Dari Ulasan
drneel
@drneel saya bisa menyalin dan menempelkan bit dari tutorial ... tetapi apakah itu tidak akan mengambil kredit dari penulis?
Jet Blue
1
@JetBlue: Biarkan tautan dalam jawaban sebagai referensi, dan sertakan bit yang relevan dalam kata-kata Anda sendiri di pos jawaban agar tidak melanggar hak cipta siapa pun. Saya tahu ini tampaknya ketat tetapi tetapi banyak jawaban telah menjadi jauh lebih baik karena aturan ini.
Flimm
6

Hari ini saya jauh di Pola Desain Negara. Saya melakukan dan menguji ThreadState, yang sama (+/-) dengan Threading di C #, seperti yang dijelaskan dalam gambar dari Threading di C #

masukkan deskripsi gambar di sini

Anda dapat dengan mudah menambahkan status baru, mengkonfigurasi perpindahan dari satu kondisi ke kondisi lainnya sangat mudah karena ini diimplementasikan dalam implementasi negara

Implementasi dan penggunaan di: Implements .NET ThreadState oleh State Design Pattern

zzfima
sumber
2
Tautan sudah mati. Anda punya yang lain?
gulungan
5

Saya belum mencoba mengimplementasikan FSM di C #, tetapi semua ini terdengar (atau terlihat) sangat rumit dengan cara saya menangani FSM di masa lalu dalam bahasa tingkat rendah seperti C atau ASM.

Saya percaya metode yang selalu saya kenal disebut sesuatu seperti "Iterative Loop". Di dalamnya, Anda pada dasarnya memiliki loop 'sementara' yang keluar secara berkala berdasarkan peristiwa (interupsi), lalu kembali ke loop utama lagi.

Di dalam interrupt handler, Anda akan melewati CurrentState dan mengembalikan NextState, yang kemudian menimpa variabel CurrentState di loop utama. Anda melakukan ini hingga tak terbatas hingga program ditutup (atau mikrokontroler diatur ulang).

Apa yang saya lihat di jawaban lain semuanya terlihat sangat rumit dibandingkan dengan bagaimana FSM, dalam pikiran saya, dimaksudkan untuk diterapkan; keindahannya terletak pada kesederhanaannya dan FSM bisa sangat rumit dengan banyak, banyak negara dan transisi, tetapi mereka memungkinkan proses rumit dengan mudah dipecah dan dicerna.

Saya menyadari bahwa tanggapan saya seharusnya tidak mencakup pertanyaan lain, tetapi saya terpaksa bertanya: mengapa solusi yang diusulkan ini tampak begitu rumit?
Mereka sepertinya mirip dengan memukul paku kecil dengan palu godam raksasa.

Dluberger
sumber
1
Sangat setuju. Perulangan while yang simpel dengan pernyataan sakelar sesederhana mungkin.
gulungan
2
Kecuali jika Anda memiliki mesin status yang sangat rumit dengan banyak kondisi dan kondisi, di mana Anda akan berakhir dengan beberapa sakelar bersarang. Juga mungkin ada penalti dalam penantian sibuk, tergantung pada implementasi loop Anda.
Sune Rievers
3

Apa yang StatePattern pertarungan. Apakah itu sesuai dengan kebutuhan Anda?

Saya pikir konteksnya terkait, tetapi nilainya patut dicoba.

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

Ini membiarkan negara Anda memutuskan ke mana harus pergi dan bukan kelas "objek".

Bruno

Bruno Bertechini
sumber
1
Pola state berhubungan dengan kelas yang dapat bertindak secara berbeda berdasarkan state / mode yang ada di dalamnya, ia tidak berurusan dengan transisi antar negara.
Eli Algranti
3

Menurut pendapat saya mesin keadaan tidak hanya dimaksudkan untuk mengubah keadaan tetapi juga (sangat penting) untuk menangani pemicu / peristiwa dalam keadaan tertentu. Jika Anda ingin memahami pola desain mesin keadaan lebih baik, deskripsi yang baik dapat ditemukan dalam buku Head First Design Patterns, halaman 320 .

Ini bukan hanya tentang status dalam variabel tetapi juga tentang penanganan pemicu dalam status berbeda. Bab yang hebat (dan tidak, tidak ada biaya bagi saya untuk menyebutkan ini :-) yang berisi penjelasan yang mudah dimengerti.

Ton Snoei
sumber
3

Saya baru saja berkontribusi ini:

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

Berikut adalah salah satu contoh demoing pengiriman perintah langsung dan tidak langsung, dengan status sebagai IObserver (sinyal), sehingga responden ke sumber sinyal, IObservable (sinyal):

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();
        }
    }
}

Catatan: contoh ini agak buatan dan sebagian besar dimaksudkan untuk mendemonstrasikan sejumlah fitur ortogonal. Seharusnya jarang ada kebutuhan nyata untuk mengimplementasikan domain nilai keadaan itu sendiri oleh kelas penuh, menggunakan CRTP (lihat: http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern ) seperti ini.

Berikut ini untuk kasus penggunaan implementasi yang tentu lebih sederhana dan mungkin jauh lebih umum (menggunakan tipe enum sederhana sebagai domain nilai status), untuk mesin status yang sama, dan dengan case uji yang sama:

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

YSharp
sumber
Bukankah agak aneh bahwa setiap instance state memiliki salinan grafik state sendiri?
Groo
@ Goo: tidak, mereka tidak. Hanya instance Televisi yang dibangun menggunakan konstruktor pribadi dengan string nol untuk moniker (karenanya, memanggil metode 'Bangun' yang dilindungi) akan memiliki grafik keadaan, sebagai mesin negara. Yang lain, bernama instance Televisi (dengan moniker tidak nol untuk tujuan konvensional dan ad-hoc) akan menjadi sekadar "titik perbaikan" negara (untuk berbicara), berfungsi sebagai konstanta negara (yang grafik negara bagian dari mesin keadaan aktual akan merujuk sebagai simpulnya). 'HTH,
YSharp
OK saya mengerti. Bagaimanapun, IMHO, akan lebih baik jika Anda memasukkan beberapa kode yang benar-benar menangani transisi ini. Dengan cara ini, ini hanya berfungsi sebagai contoh menggunakan antarmuka (IMHO) yang tidak terlalu jelas untuk perpustakaan Anda. Misalnya, bagaimana cara StateChangediselesaikan? Melalui Refleksi? Apakah itu benar-benar perlu?
Groo
1
@ Goo: Ucapan baik. Memang tidak perlu untuk merefleksikan handler dalam contoh pertama karena dilakukan secara terprogram di sana secara tepat dan dapat diikat secara statis / ketik diperiksa (tidak seperti ketika melalui atribut kustom). Jadi pekerjaan ini seperti yang diharapkan juga: 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
Terima kasih atas usaha Anda!
Groo
3

Saya membuat mesin keadaan umum ini dari kode Juliet. Ini bekerja luar biasa bagi saya.

Inilah manfaatnya:

  • Anda dapat membuat mesin negara baru dalam kode dengan dua enum TStatedan TCommand,
  • menambahkan struct TransitionResult<TState>untuk memiliki kontrol lebih besar atas hasil keluaran [Try]GetNext()metode
  • mengekspos kelas bersarang StateTransition hanya melalui AddTransition(TState, TCommand, TState)membuatnya lebih mudah untuk bekerja dengannya

Kode:

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;
    }
}

Ini adalah jenis pengembalian metode TryGetNext:

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

Cara Penggunaan:

Ini adalah bagaimana Anda dapat membuat OnlineDiscountStateMachinedari kelas generik:

Tentukan enum OnlineDiscountStateuntuk statusnya dan enum OnlineDiscountCommanduntuk perintahnya.

Tentukan kelas yang OnlineDiscountStateMachineberasal dari kelas generik menggunakan kedua enum tersebut

Turunkan konstruktor dari base(OnlineDiscountState.InitialState)sehingga keadaan awal diatur keOnlineDiscountState.InitialState

Gunakan AddTransitionsebanyak yang diperlukan

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);
    }
}

menggunakan mesin negara turunan

    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);
    }
Bizhan
sumber
1

Saya pikir mesin negara yang diusulkan oleh Juliet memiliki kesalahan: metode GetHashCode dapat mengembalikan kode hash yang sama untuk dua transisi yang berbeda, misalnya:

Status = Aktif (1), Perintah = Jeda (2) => HashCode = 17 + 31 + 62 = 110

State = Paused (2), Command = End (1) => HashCode = 17 + 62 + 31 = 110

Untuk menghindari kesalahan ini, metodenya harus seperti ini:

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

Alex

alexag
sumber
1
Kode hash tidak diperlukan untuk mengembalikan nomor unik untuk setiap kombinasi yang mungkin, hanya nilai yang berbeda dengan distribusi yang baik di seluruh rentang target (dalam hal ini kisaran adalah semua nilai yang mungkin int). Itu sebabnya HashCodeselalu diimplementasikan bersama Equals. Jika kode hash adalah sama, maka objek diperiksa untuk eqaulity yang tepat menggunakan Equalsmetode ini.
Dmitry Avtonomov
0

FiniteStateMachine adalah Mesin Status Sederhana, ditulis dalam C # Link

Keuntungan tu menggunakan perpustakaan saya, FiniteStateMachine:

  1. Tentukan kelas "konteks" untuk menyajikan antarmuka tunggal ke dunia luar.
  2. Tentukan kelas dasar abstrak Negara.
  3. Mewakili "keadaan" yang berbeda dari mesin negara sebagai kelas turunan dari kelas dasar Negara.
  4. Definisikan perilaku spesifik-negara di kelas-kelas State yang sesuai.
  5. Pertahankan pointer ke "state" saat ini di kelas "context".
  6. Untuk mengubah status mesin status, ubah pointer "status" saat ini.

Unduh DLL Unduh

Contoh pada 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;


     }

 }
Domenico Zinzi
sumber
1
Ini memiliki lisensi GNU GPL.
Der_Meister
0

Saya akan merekomendasikan state.cs . Saya pribadi menggunakan state.js (versi JavaScript) dan saya sangat senang dengannya. Versi C # itu bekerja dengan cara yang serupa.

Anda memberi contoh status:

        // 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" );
        ...

Anda instantiate beberapa transisi:

        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" ) );

Anda mendefinisikan tindakan pada negara bagian dan transisi:

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

Dan itu (cukup banyak) itu. Lihatlah situs web untuk informasi lebih lanjut.

bmorin
sumber
0

Ada 2 paket mesin negara yang populer di NuGet.

Appccelerate.StateMachine (13.6K unduhan + 3.82K versi lawas (bbv.Common.StateMachine))

StateMachineToolkit (1.56K unduhan)

Lib Appccelerate memiliki dokumentasi yang baik , tetapi tidak mendukung .NET 4, jadi saya memilih StateMachineToolkit untuk proyek saya.

Der_Meister
sumber
0

Alternatif lain dalam repo ini https://github.com/lingkodsoft/StateBliss menggunakan sintaks yang lancar, mendukung pemicu.

    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
    }
}
mcdm
sumber
0

Anda dapat menggunakan solusi saya, ini adalah cara yang paling nyaman. Ini juga gratis.

Buat mesin negara dalam tiga langkah:

1. Buat skema dalam editor simpul🔗 dan muat dalam proyek Anda menggunakan perpustakaan📚

StateMachine stateMachine = StateMachine baru ("scheme.xml");

2. Jelaskan logika aplikasi Anda pada acara⚡

stateMachine.GetState ("State1"). OnExit (Action1);
stateMachine.GetState ("State2"). OnEntry (Action2);
stateMachine.GetTransition ("Transition1"). OnInvoke (Action3);
stateMachine.OnChangeState (Action4);

3. Jalankan mesin negara🚘

stateMachine.Start ();

Tautan:

Editor simpul: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

Perpustakaan: https://github.com/SimpleStateMachine/SimpleStateMachineLibrary

GMIKE
sumber