Ví dụ máy trạng thái đơn giản trong C #?


257

Cập nhật:

Một lần nữa cảm ơn các ví dụ, chúng rất hữu ích và với những điều sau đây tôi không có ý lấy đi bất cứ thứ gì từ chúng.

Không phải là những ví dụ hiện tại, theo như tôi hiểu về chúng và các máy trạng thái, chỉ bằng một nửa những gì chúng ta thường hiểu bởi một máy trạng thái?
Theo nghĩa là các ví dụ thay đổi trạng thái nhưng điều đó chỉ được biểu thị bằng cách thay đổi giá trị của biến (và cho phép thay đổi giá trị khác nhau ở các trạng thái khác nhau), trong khi thông thường, một máy trạng thái cũng nên thay đổi hành vi và hành vi không (chỉ) trong ý nghĩa cho phép thay đổi giá trị khác nhau cho một biến tùy thuộc vào trạng thái, nhưng theo nghĩa là cho phép các phương thức khác nhau được thực thi cho các trạng thái khác nhau.

Hay tôi có một quan niệm sai lầm về máy móc nhà nước và sử dụng chung của chúng?

Trân trọng


Câu hỏi gốc:

Tôi đã tìm thấy cuộc thảo luận này về các máy trạng thái & khối lặp trong c # và các công cụ để tạo ra các máy trạng thái và những gì không dành cho C #, vì vậy tôi đã tìm thấy rất nhiều nội dung trừu tượng nhưng vì tất cả điều này hơi khó hiểu.

Vì vậy, sẽ thật tuyệt nếu ai đó có thể cung cấp một ví dụ mã nguồn C # để nhận ra một máy trạng thái đơn giản có lẽ có 3,4 trạng thái, chỉ để có được ý chính của nó.



Bạn đang tự hỏi về các máy trạng thái nói chung hoặc chỉ các máy dựa trên iterator?
Skurmedel

2
Có Net Lõi Stateless lib với các ví dụ, DAG daigram vv - giá trị reviewing: hanselman.com/blog/...
zmische

Câu trả lời:


416

Hãy bắt đầu với sơ đồ trạng thái đơn giản này:

sơ đồ máy trạng thái đơn giản

Chúng ta có:

  • 4 trạng thái (Không hoạt động, Đang hoạt động, Tạm dừng và Đã thoát)
  • 5 loại chuyển trạng thái (Lệnh bắt đầu, Lệnh kết thúc, Lệnh tạm dừng, Lệnh tiếp tục, Lệnh thoát).

Bạn có thể chuyển đổi nó thành C # theo một số cách, chẳng hạn như thực hiện một câu lệnh chuyển đổi trên trạng thái và lệnh hiện tại hoặc tìm kiếm các chuyển đổi trong bảng chuyển đổi. Đối với máy trạng thái đơn giản này, tôi thích một bảng chuyển đổi, rất dễ biểu diễn bằng cách sử dụng 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();
        }
    }
}

Theo sở thích cá nhân, tôi thích thiết kế các máy trạng thái của mình với GetNextchức năng trả về trạng thái tiếp theo một cách xác định và một MoveNextchức năng để thay đổi máy trạng thái.


66
+1 để thực hiện đúng việc GetHashCode()sử dụng các số nguyên tố.
ja72

13
Bạn có thể vui lòng giải thích cho tôi mục đích của GetHashCode () không?
Siddharth

14
@Siddharth: StateTransitionLớp được sử dụng làm khóa trong từ điển và sự bình đẳng của các khóa rất quan trọng. Hai trường hợp riêng biệt StateTransitionnên được coi là bằng nhau miễn là chúng đại diện cho cùng một quá trình chuyển đổi (ví dụ CurrentStateCommandgiống nhau). Để thực hiện bình đẳng, bạn phải ghi đè Equalscũng như GetHashCode. Cụ thể, từ điển sẽ sử dụng mã băm và hai đối tượng bằng nhau phải trả về cùng mã băm. Bạn cũng có được hiệu suất tốt nếu không có quá nhiều đối tượng không bằng nhau chia sẻ cùng mã băm, đó là lý do tại sao GetHashCodeđược triển khai như được hiển thị.
Martin Liversage

14
Mặc dù điều này chắc chắn mang lại cho bạn một cỗ máy trạng thái (và cũng là một triển khai C # 'phù hợp), tôi cảm thấy nó vẫn còn thiếu câu trả lời cho câu hỏi của OP về việc thay đổi hành vi? Rốt cuộc, nó chỉ tính toán các trạng thái nhưng hành vi liên quan đến thay đổi trạng thái, phần thịt thực sự của chương trình và thường được gọi là sự kiện Entry / Exit, vẫn còn thiếu.
stijn

2
Nếu ai đó sẽ cần nó: Tôi đã điều chỉnh cỗ máy tate này và sử dụng nó trong trò chơi đoàn kết của mình. Nó có sẵn trên git hub: github.com/MarcoMig/Finite-State-Machine-FSM
Max_Power89

73

Bạn có thể muốn sử dụng một trong các Máy trạng thái hữu hạn nguồn mở hiện có. Ví dụ: bbv.Common.StateMachine được tìm thấy tại http://code.google.com.vn/p/bbvcommon/wiki/StateMachine . Nó có một cú pháp lưu loát rất trực quan và rất nhiều tính năng như, hành động nhập / thoát, hành động chuyển tiếp, bảo vệ, phân cấp, thực hiện thụ động (được thực hiện trên luồng của người gọi) và thực hiện chủ động (luồng riêng mà fsm chạy, các sự kiện được thêm vào hàng đợi).

Lấy ví dụ Juliets định nghĩa cho máy trạng thái trở nên rất dễ dàng:

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

Cập nhật : Vị trí dự án đã được chuyển đến: https://github.com/appccelates/statemachine


4
Cảm ơn bạn đã tham khảo máy trạng thái nguồn mở tuyệt vời này. Tôi có thể hỏi làm thế nào tôi có thể có được trạng thái hiện tại?
Ramazan Polat

3
Bạn không thể và bạn không nên. Nhà nước là một cái gì đó không ổn định. Khi bạn yêu cầu trạng thái, có thể bạn đang ở giữa một quá trình chuyển đổi. Tất cả các hành động nên được thực hiện trong quá trình chuyển đổi, nhập cảnh và thoát khỏi trạng thái. Nếu bạn thực sự muốn có trạng thái thì bạn có thể thêm một trường cục bộ và gán trạng thái trong một hành động nhập cảnh.
Remo Gloor

4
Câu hỏi là vì cái gì bạn "cần" nó và nếu bạn thực sự cần trạng thái SM hoặc một loại trạng thái nào khác. Ví dụ: nếu bạn cần một số văn bản hiển thị thì một số đã nêu có thể có cùng một văn bản hiển thị, ví dụ nếu chuẩn bị gửi có nhiều trạng thái phụ. Trong trường hợp này bạn nên làm chính xác những gì bạn định làm. Cập nhật một số văn bản hiển thị ở những nơi chính xác. Ví dụ: trong ExecuteOnEntry. Nếu bạn cần thêm thông tin thì hãy hỏi một Câu hỏi mới và nêu chính xác vấn đề của bạn vì điều này đang lạc đề ở đây.
Remo Gloor

Ok tôi đang hỏi một câu hỏi mới và chờ bạn trả lời. Bởi vì tôi không nghĩ ai đó sẽ giải quyết vấn đề này vì bạn có câu trả lời tốt nhất nhưng người hỏi vẫn không chấp nhận. Tôi sẽ đăng url câu hỏi ở đây. Cảm ơn.
Ramazan Polat

4
+1 cho API thông thạo và khai báo. Thật tuyệt vời. BTW, mã google dường như đã lỗi thời. Trang web dự án mới nhất của họ là trên GitHub tại đây
KFL

51

Đây là một ví dụ về một máy trạng thái hữu hạn rất cổ điển, mô hình hóa một thiết bị điện tử rất đơn giản (như 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; }
    }
}
}

6
Đối với bất kỳ ai mới sử dụng máy trạng thái, đây là một ví dụ tuyệt vời đầu tiên để làm ướt chân trước.
Tích cực,

2
Tôi mới làm quen với máy móc nhà nước và nghiêm túc, điều này đã mang lại cho tôi The Light - cảm ơn!
MC5

1
Tôi thích thực hiện này. Đối với bất cứ ai có thể vấp ngã về điều này, một "cải tiến" nhẹ. Trong lớp FSM, tôi đã thêm private void DoNothing() {return;}và thay thế tất cả các trường hợp null bằng this.DoNothing. Có tác dụng phụ dễ chịu của việc trả lại trạng thái hiện tại.
Sethmo011

1
Tôi tự hỏi nếu có một lý do đằng sau một số tên này. Khi tôi nhìn vào điều này, trực giác đầu tiên của tôi là để đổi tên các yếu tố của Statesđể Unpowered, Standby, On. Lý do của tôi là nếu ai đó hỏi tôi trạng thái tivi của tôi ở đâu, tôi sẽ nói "Tắt" chứ không phải "Bắt đầu". Tôi cũng thay đổi StandbyWhenOnStandbyWhenOffđến TurnOnTurnOff. Điều đó làm cho mã đọc trực quan hơn, nhưng tôi tự hỏi liệu có những quy ước hoặc các yếu tố khác làm cho thuật ngữ của tôi không phù hợp.
Jason Hamje

Có vẻ hợp lý, tôi đã không thực sự tuân theo bất kỳ quy ước đặt tên nhà nước nào; Tên như có ý nghĩa cho bất cứ điều gì bạn mô hình.
Pete Stensønes

20

Một số tự quảng cáo không biết xấu hổ ở đây, nhưng một thời gian trước, tôi đã tạo ra một thư viện có tên YieldMachine cho phép một máy trạng thái phức tạp giới hạn được mô tả một cách rất sạch sẽ và đơn giản. Ví dụ, hãy xem xét một đèn:

máy trạng thái của đèn

Lưu ý rằng máy trạng thái này có 2 kích hoạt và 3 trạng thái. Trong mã YieldMachine, chúng tôi viết một phương thức duy nhất cho tất cả các hành vi liên quan đến trạng thái, trong đó chúng tôi thực hiện hành vi tàn bạo khủng khiếp khi sử dụng gotocho mỗi trạng thái. Một kích hoạt trở thành một thuộc tính hoặc trường loạiAction , được trang trí với một thuộc tính được gọi Trigger. Tôi đã nhận xét mã của trạng thái đầu tiên và các chuyển đổi của nó bên dưới; các trạng thái tiếp theo theo cùng một mô hình.

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

Ngắn và tốt, eh!

Máy trạng thái này được điều khiển đơn giản bằng cách gửi các kích hoạt đến nó:

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

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

Để làm rõ, tôi đã thêm một số ý kiến ​​vào trạng thái đầu tiên để giúp bạn hiểu cách sử dụng này.

    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

        ...

Điều này hoạt động vì trình biên dịch C # thực sự tạo ra một máy trạng thái bên trong cho mỗi phương thức sử dụng yield return . Cấu trúc này thường được sử dụng để lười biếng tạo ra các chuỗi dữ liệu, nhưng trong trường hợp này, chúng tôi không thực sự quan tâm đến chuỗi được trả về (dù sao tất cả đều là null), nhưng trong hành vi trạng thái được tạo ra dưới mui xe.

Lớp StateMachinecơ sở thực hiện một số phản ánh về xây dựng để gán mã cho mỗi[Trigger] hành động, điều này đặt Triggerthành viên và di chuyển máy trạng thái về phía trước.

Nhưng bạn không thực sự cần phải hiểu nội bộ để có thể sử dụng nó.


2
"Goto" chỉ tàn bạo nếu nó nhảy giữa các phương thức. Điều đó, may mắn thay, không được phép trong C #.
Brannon

Điểm tốt! Trong thực tế, tôi sẽ rất ấn tượng nếu bất kỳ ngôn ngữ gõ tĩnh nào sẽ quản lý để cho phép gotogiữa các phương thức.
skrebbel

3
@Brannon: ngôn ngữ nào cho phép gotonhảy giữa các phương thức? Tôi không thấy nó sẽ hoạt động như thế nào. Không, gotocó vấn đề vì nó dẫn đến lập trình thủ tục (chính điều này làm phức tạp những điều tốt đẹp như kiểm thử đơn vị), thúc đẩy sự lặp lại mã (nhận thấy InvalidTriggercần phải chèn như thế nào cho mọi trạng thái?) Và cuối cùng làm cho chương trình khó theo dõi hơn. So sánh điều này với (hầu hết) các giải pháp khác trong chuỗi này và bạn sẽ thấy rằng đây là giải pháp duy nhất mà toàn bộ FSM xảy ra trong một phương thức duy nhất. Điều đó thường đủ để gây lo ngại.
Groo

1
@Groo, GW-BASIC chẳng hạn. Nó giúp nó không có phương thức, hoặc thậm chí chức năng. Bên cạnh đó, tôi có một thời gian rất khó hiểu tại sao bạn thấy "dòng chảy chương trình khó theo dõi hơn" trong ví dụ này. Đó là một cỗ máy trạng thái, "đi đến" một trạng thái từ một trạng thái khác là điều duy nhất bạn làm. Bản đồ này gotokhá tốt.
skrebbel

3
GW-BASIC cho phép gotonhảy giữa các chức năng, nhưng nó không hỗ trợ các chức năng? :) Bạn nói đúng, nhận xét "khó theo dõi" là gotovấn đề chung hơn , thực sự không có vấn đề gì trong trường hợp này.
Groo

13

Bạn có thể mã hóa một khối lặp cho phép bạn thực thi một khối mã theo kiểu được phối hợp. Làm thế nào khối mã bị phá vỡ thực sự không phải tương ứng với bất cứ điều gì, đó chỉ là cách bạn muốn mã hóa nó. Ví dụ:

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

Trong trường hợp này, khi bạn gọi CountToTen, thực tế vẫn chưa có gì thực thi. Những gì bạn nhận được thực sự là một trình tạo máy trạng thái, mà bạn có thể tạo một phiên bản mới của máy trạng thái. Bạn làm điều này bằng cách gọi GetEnumerator (). IEnumerator kết quả là một máy trạng thái mà bạn có thể lái xe bằng cách gọi MoveNext (...).

Do đó, trong ví dụ này, lần đầu tiên bạn gọi MoveNext (...) bạn sẽ thấy "1" được ghi vào bảng điều khiển và lần tiếp theo bạn gọi MoveNext (...) bạn sẽ thấy 2, 3, 4 và sau đó 5, 6, 7 và 8, rồi 9, 10. Như bạn có thể thấy, đó là một cơ chế hữu ích để sắp xếp cách mọi thứ sẽ xảy ra.


6
Liên kết bắt buộc để cảnh báo công bằng
sehe

8

Tôi đang đăng một câu trả lời khác ở đây vì đây là máy trạng thái từ một góc nhìn khác; rất trực quan.

Câu trả lời ban đầu của tôi là mã mệnh lệnh cổ điển. Tôi nghĩ rằng nó khá trực quan như mã đi vì mảng làm cho việc trực quan hóa máy trạng thái đơn giản. Nhược điểm là bạn phải viết tất cả điều này. Câu trả lời của Remos làm giảm bớt nỗ lực viết mã nồi hơi nhưng ít trực quan hơn. Có sự thay thế thứ ba; thực sự vẽ máy nhà nước.

Nếu bạn đang sử dụng .NET và có thể nhắm mục tiêu phiên bản 4 của thời gian chạy thì bạn có tùy chọn sử dụng các hoạt động máy trạng thái của dòng công việc . Những thứ này về bản chất cho phép bạn vẽ máy trạng thái (giống như trong sơ đồ của Juliet ) và để thời gian chạy WF thực hiện nó cho bạn.

Xem bài viết MSDN Xây dựng các máy trạng thái với Windows Workflow Foundation để biết thêm chi tiết và trang CodePlex này để biết phiên bản mới nhất.

Đó là tùy chọn tôi luôn thích khi nhắm mục tiêu .NET vì dễ thấy, thay đổi và giải thích cho những người không lập trình; hình ảnh đáng giá ngàn lời nói như họ nói!


Tôi nghĩ rằng bộ máy nhà nước là một trong những phần tốt nhất của toàn bộ nền tảng công việc!
fabsenet

7

Thật hữu ích khi nhớ rằng các máy trạng thái là một sự trừu tượng và bạn không cần các công cụ cụ thể để tạo một công cụ, tuy nhiên các công cụ có thể hữu ích.

Ví dụ, bạn có thể nhận ra một máy trạng thái với các chức năng:

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

Cỗ máy này sẽ săn những con mòng biển và cố gắng đánh chúng bằng bóng nước. Nếu nó bỏ lỡ, nó sẽ thử bắn một phát cho đến khi nó bắn trúng (có thể làm với một số kỳ vọng thực tế;)), nếu không nó sẽ hả hê trong bảng điều khiển. Nó tiếp tục săn mồi cho đến khi hết hải âu để quấy rối.

Mỗi chức năng tương ứng với từng tiểu bang; trạng thái bắt đầu và kết thúc (hoặc chấp nhận ) không được hiển thị. Có lẽ có nhiều trạng thái trong đó hơn được mô hình hóa bởi các chức năng mặc dù. Ví dụ, sau khi bắn bong bóng, cỗ máy thực sự ở trạng thái khác so với trước đó, nhưng tôi quyết định sự khác biệt này là không thực tế.

Một cách phổ biến là sử dụng các lớp để biểu diễn các trạng thái và sau đó kết nối chúng theo các cách khác nhau.


7

Tìm thấy hướng dẫn tuyệt vời này trực tuyến và nó giúp tôi quấn đầu quanh những cỗ máy trạng thái hữu hạn.

http://gamedevelopment.tutsplus.com/tutorials/finite-state-machines-theory-and-im THỰCation--gamedev-11867

Hướng dẫn là bất khả tri về ngôn ngữ, vì vậy nó có thể dễ dàng được điều chỉnh theo nhu cầu C # của bạn.

Ngoài ra, ví dụ được sử dụng (một con kiến ​​tìm kiếm thức ăn) là dễ hiểu.


Từ hướng dẫn:

nhập mô tả hình ảnh ở đây

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
Mặc dù liên kết này có thể trả lời câu hỏi, tốt hơn là bao gồm các phần thiết yếu của câu trả lời ở đây và cung cấp liên kết để tham khảo. Câu trả lời chỉ liên kết có thể trở nên không hợp lệ nếu trang được liên kết thay đổi. - Từ đánh giá
drneel

@drneel Tôi có thể sao chép và dán các bit từ hướng dẫn ... nhưng điều đó sẽ không lấy tín dụng từ tác giả?
Jet Blue

1
@JetBlue: Để lại liên kết trong câu trả lời làm tài liệu tham khảo và bao gồm các bit có liên quan trong từ của bạn trong bài trả lời để không phá vỡ bản quyền của bất kỳ ai. Tôi biết nó có vẻ nghiêm ngặt nhưng nhiều câu trả lời đã trở nên tốt hơn nhiều vì quy tắc này.
Flimm

6

Hôm nay tôi sâu trong mẫu thiết kế nhà nước. Tôi đã làm và kiểm tra ThreadState, bằng (+/-) với Threading trong C #, như được mô tả trong hình từ Threading trong C #

nhập mô tả hình ảnh ở đây

Bạn có thể dễ dàng thêm các trạng thái mới, cấu hình di chuyển từ trạng thái này sang trạng thái khác rất dễ dàng vì nó được bao gồm trong việc thực hiện trạng thái

Triển khai và sử dụng tại: Triển khai .NET ThreadState theo mẫu thiết kế trạng thái


1
Liên kết đã chết. Bạn có cái khác không?
cuộn

5

Tôi chưa thử triển khai một FSM bằng C #, nhưng tất cả những âm thanh (hoặc nhìn) này rất phức tạp đối với cách tôi xử lý các FSM trong quá khứ bằng các ngôn ngữ cấp thấp như C hoặc ASM.

Tôi tin rằng phương pháp mà tôi luôn biết được gọi là "Vòng lặp lặp". Trong đó, về cơ bản, bạn có một vòng lặp 'while' thoát ra định kỳ dựa trên các sự kiện (ngắt), sau đó quay lại vòng lặp chính.

Trong các trình xử lý ngắt, bạn sẽ vượt qua CurrentState và trả về NextState, sau đó ghi đè lên biến CurrentState trong vòng lặp chính. Bạn thực hiện quảng cáo này cho đến khi chương trình đóng lại (hoặc bộ vi điều khiển đặt lại).

Tất cả những gì tôi đang thấy các câu trả lời khác đều trông rất phức tạp so với cách mà một FSM, trong suy nghĩ của tôi, dự định được thực hiện; Vẻ đẹp của nó nằm ở sự đơn giản của nó và FSM có thể rất phức tạp với nhiều, nhiều trạng thái và quá trình chuyển đổi, nhưng chúng cho phép quá trình phức tạp dễ dàng bị phá vỡ và tiêu hóa.

Tôi nhận ra câu trả lời của mình không nên bao gồm một câu hỏi khác, nhưng tôi buộc phải hỏi: tại sao những giải pháp đề xuất khác này lại có vẻ phức tạp như vậy?
Chúng dường như giống như đánh một cây đinh nhỏ bằng búa tạ khổng lồ.


1
Hoàn toàn đồng ý. Một vòng lặp while đơn giản với câu lệnh switch đơn giản như bạn có thể nhận được.
cuộn

2
Trừ khi bạn có một máy trạng thái rất phức tạp với nhiều trạng thái và điều kiện, nơi bạn sẽ kết thúc với nhiều công tắc lồng nhau. Ngoài ra, có thể có một hình phạt trong chờ đợi bận rộn, tùy thuộc vào việc thực hiện vòng lặp của bạn.
Sune Rievers

3

Thật là một tiểu bang. Điều đó có phù hợp với nhu cầu của bạn không?

Tôi nghĩ bối cảnh của nó có liên quan, nhưng nó đáng để thử.

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

Điều này cho phép các tiểu bang của bạn quyết định nơi để đi và không phải là lớp "đối tượng".

Bruno


1
Mẫu trạng thái xử lý một lớp có thể hoạt động khác nhau dựa trên trạng thái / chế độ mà nó đang ở, nó không xử lý chuyển đổi giữa các trạng thái.
Eli Algranti

3

Theo tôi, một máy trạng thái không chỉ có nghĩa là để thay đổi trạng thái mà còn (rất quan trọng) để xử lý các kích hoạt / sự kiện trong một trạng thái cụ thể. Nếu bạn muốn hiểu mẫu thiết kế máy trạng thái tốt hơn, có thể tìm thấy một mô tả hay trong cuốn sách Head First Design Forms, trang 320 .

Nó không chỉ là về các trạng thái trong các biến mà còn về việc xử lý các kích hoạt trong các trạng thái khác nhau. Chương tuyệt vời (và không, không có phí cho tôi khi đề cập đến điều này :-) chỉ chứa một lời giải thích dễ hiểu.


3

Tôi vừa mới đóng góp cái này:

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

Đây là một trong những ví dụ trình diễn việc gửi lệnh trực tiếp và gián tiếp, với các trạng thái là IObserver (của tín hiệu), do đó đáp ứng với nguồn tín hiệu, IObservable (của tín hiệu):

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

Lưu ý: ví dụ này khá giả tạo và chủ yếu là để giới thiệu một số tính năng trực giao. Rất hiếm khi cần phải thực hiện chính miền giá trị trạng thái bằng một lớp đầy đủ, sử dụng CRTP (xem: http://en.wikipedia.org/wiki/Cantlyly_recurring_template_potype ) như thế này.

Đây là trường hợp sử dụng triển khai thực hiện đơn giản và có khả năng phổ biến hơn nhiều (sử dụng loại enum đơn giản làm miền giá trị trạng thái), cho cùng một máy trạng thái và với cùng một trường hợp thử nghiệm:

https://code.google.com.vn/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


Không có gì lạ khi mỗi thể hiện trạng thái có bản sao riêng của biểu đồ trạng thái?
Groo

@Groo: không, họ không. Chỉ các phiên bản của Truyền hình được xây dựng bằng cách sử dụng hàm tạo riêng với chuỗi null cho biệt danh (do đó, gọi phương thức 'Xây dựng' được bảo vệ) mới có biểu đồ trạng thái, như các máy trạng thái. Các trường hợp khác, được đặt tên là Truyền hình (với biệt danh không phải là mục đích thông thường và đặc biệt) sẽ chỉ là các trạng thái "điểm cố định" (có thể nói), đóng vai trò là hằng số trạng thái (biểu đồ trạng thái của máy trạng thái thực tế sẽ tham chiếu như các đỉnh của chúng). 'HTH,
YSharp

OK tôi hiểu rồi. Dù sao, IMHO, sẽ tốt hơn nếu bạn bao gồm một số mã thực sự xử lý các chuyển đổi này. Theo cách này, nó chỉ đóng vai trò là một ví dụ về việc sử dụng giao diện không rõ ràng (IMHO) cho thư viện của bạn. Chẳng hạn, StateChangegiải quyết thế nào? Qua suy ngẫm? Điều đó có thực sự cần thiết?
Groo

1
@Groo: Nhận xét tốt. Thực sự không cần thiết phải phản ánh về trình xử lý trong ví dụ đầu tiên đó bởi vì nó được thực hiện theo chương trình ở đó một cách chính xác và có thể được ràng buộc tĩnh / loại kiểm tra (không giống như khi thông qua các thuộc tính tùy chỉnh). Vì vậy, công việc này cũng được mong đợi: 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
Cảm ơn nỗ lực của bạn!
Groo

3

Tôi đã tạo ra máy trạng thái chung này từ mã của Juliet. Nó làm việc tuyệt vời đối với tôi.

Đây là những lợi ích:

  • bạn có thể tạo máy trạng thái mới bằng mã với hai enum TStateTCommand,
  • thêm struct TransitionResult<TState>để có nhiều quyền kiểm soát hơn đối với kết quả đầu ra của các [Try]GetNext()phương thức
  • StateTransition chỉ hiển thị lớp lồng nhau thông qua AddTransition(TState, TCommand, TState)việc làm việc với nó dễ dàng hơn

Mã số:

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

Đây là kiểu trả về của phương thức TryGetNext:

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

Cách sử dụng:

Đây là cách bạn có thể tạo một OnlineDiscountStateMachinetừ lớp chung:

Xác định một enum OnlineDiscountStatecho các trạng thái của nó và một enum OnlineDiscountCommandcho các lệnh của nó.

Xác định một lớp OnlineDiscountStateMachinecó nguồn gốc từ lớp chung bằng cách sử dụng hai enum đó

Xuất phát từ hàm tạo base(OnlineDiscountState.InitialState)để trạng thái ban đầu được đặt thànhOnlineDiscountState.InitialState

Sử dụng AddTransitionnhiều lần nếu cần

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

sử dụng máy trạng thái dẫn xuất

    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

Tôi nghĩ rằng máy trạng thái do Juliet đề xuất có một lỗi: phương thức GetHashCode có thể trả về cùng mã băm cho hai lần chuyển đổi khác nhau, ví dụ:

Trạng thái = Hoạt động (1), Lệnh = Tạm dừng (2) => HashCode = 17 + 31 + 62 = 110

Trạng thái = Tạm dừng (2), Lệnh = Kết thúc (1) => HashCode = 17 + 62 + 31 = 110

Để tránh lỗi này, phương thức nên như thế này:

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

Alex


1
Mã băm không bắt buộc phải trả về một số duy nhất cho bất kỳ kết hợp nào có thể, chỉ một giá trị riêng biệt có phân phối tốt trên phạm vi mục tiêu (trong trường hợp này là phạm vi là tất cả các intgiá trị có thể ). Đó là lý do tại sao HashCodeluôn luôn được thực hiện cùng với Equals. Nếu mã băm là như nhau, thì các đối tượng được kiểm tra chính xác hiệu quả bằng cách sử dụng Equalsphương thức.
Dmitry Avtonomov

0

FiniteStateMachine là một máy trạng thái đơn giản, được viết bằng C # Link

Ưu điểm sử dụng thư viện của tôi FiniteStateMachine:

  1. Xác định một lớp "bối cảnh" để trình bày một giao diện duy nhất với thế giới bên ngoài.
  2. Xác định một lớp cơ sở trừu tượng Nhà nước.
  3. Đại diện cho các "trạng thái" khác nhau của máy trạng thái là các lớp dẫn xuất của lớp cơ sở Nhà nước.
  4. Xác định hành vi cụ thể của nhà nước trong các lớp có nguồn gốc Nhà nước thích hợp.
  5. Duy trì một con trỏ đến "trạng thái" hiện tại trong lớp "bối cảnh".
  6. Để thay đổi trạng thái của máy trạng thái, thay đổi con trỏ "trạng thái" hiện tại.

Tải xuống DLL Tải xuống

Ví dụ về 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
Nó có giấy phép GNU GPL.
Der_Meister

0

Tôi muốn giới thiệu state.cs . Cá nhân tôi đã sử dụng state.js (phiên bản JavaScript) và rất hài lòng với nó. Phiên bản C # đó hoạt động theo cách tương tự.

Bạn khởi tạo trạng thái:

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

Bạn khởi tạo một số chuyển đổi:

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

Bạn xác định hành động trên các trạng thái và chuyển tiếp:

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

Và đó là (khá nhiều) nó. Nhìn vào trang web để biết thêm thông tin.



0

Thay thế khác trong repo này https://github.com/lingkodsoft/StateBliss sử dụng cú pháp lưu loát, hỗ trợ kích hoạt.

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

0

Bạn có thể sử dụng giải pháp của tôi, đây là cách thuận tiện nhất. Nó cũng miễn phí.

Tạo máy trạng thái theo ba bước:

1. Tạo lược đồ trong trình soạn thảo nút và tải nó trong dự án của bạn bằng thư viện📚

StateMachine stateMachine = new StateMachine ("lược đồ");

2. Mô tả logic ứng dụng của bạn về các sự kiện⚡

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

3. Chạy máy trạng thái🚘

stateMachine.Start ();

Liên kết:

Trình chỉnh sửa nút: https://github.com/SimpleStateMachine/SimpleStateMachineNodeEditor

Thư viện: https://github.com/SimpleStateMachine/SimpleStateMachineL Library

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.