Cześć, muszę dość mocno rozwinąć swoją aplikację napisaną dawno temu w C# w WinForms.

Update ma dotyczyć zmian i rozszerzeń różnych zachowań aplikacji podczas pracy, intuicyjnie widzę ze musze to przepisać do WPF (będzie mi łatwiej z wiązaniem danych na UI) oraz chyba musze zastosować maszynę stanów do modelowania zachowań aplikacji.
Nie używałem takiego wzorca jeszcze, przetestowałem go sobie pisząc standardowy kołowrotek wejścia do metra w WPF MVVM. (kod poniżej z pytaniem o kontroler który ma być wywoływany podczas zmian stanów).

Ale program jest zdecydowanie bardziej rozbudowany, składa się z okna głównego i przełączanych w nim 3 powidoków.

  1. pierwszy podwidok jest do pobrania danych pracownika
  2. po poprawnym pobraniu danych pracownika przełącza się na podwidok pobierania danych zlecenia produkcyjnego
  3. a po prawidłowym ustawieniu danych zlecenia pracownika przełącza sie na podwidok główny który służy do drukowania etykiet z numerem fabrycznym oraz w tle sprawdza dane wyroby dla którego sczytany jest numer fabryczny.

I teraz architektura wyłania mi się taka że chyba musze mieć parę maszyn stanów.

  1. jedną główną która będzie odpowiedzialna za przełączanie powidoków.
  2. i trzy kolejne dla każdego z podwidokow gdzie będą odpowiedzialne za przełączanie stanów każdego z podwidokow.

PYTANIE 1.
Czy tak się robi? czy maszyna stanów dla aplikacji ma być jedna? Widzę teoretycznie ze próbując rozpisać jedna matryce zmian stanów przy założeniu istnienia jednej maszyny stanów ta matryca jest makabrycznie duża ale ma pewne korzyści przy sterowaniu zrachowaniami aplikacji

Generalnie cały program jest obsługiwany tylko czytnikiem kodów kreskowych, użytkownik nie musi nic 'klawiaturowac' ani 'myszkowac'. mam po prostu rozpoznane typy odczytów czytnikiem i w oparciu o nie mam generowane "wyzwalacze" zmian stanów maszyny stanów. (rozkazy czytnika są albo tekstem np. "Zatwierdz" lub "Odswiez" albo wyrażeniem regularnym które jest inne dla rozpoznania ze to kod pracownika lub ze to numer fabryczny lub numer zlecenia produkcyjnego)
PYTANIE 2
W tym podejściu mam problem z określeniem stanów i wyzwalaczy zmian stanów:
czy wyzwalaczami maja być tylko odczyty czytnika czy również wyniki logiki biznesowej pracującej w tle?
np. odczyt numeru fabrycznego powoduje wydruk etykiety oraz program wraca do oczekiwania na drugi odczyt drugiego numeru fabrycznego. ale w tle prócz wydruku etykiety dla tego numeru fabrycznego jest logika która sprawdza po tym numerze fabrycznym czy to właściwy wyrób i jeżeli właściwy to wydrukuje etykietę a jeżeli nie właściwy to wyświetla komunikat ze powstał błąd , cofnie stan do oczekiwania na kolejny odczyt czytnikiem i nie wydrukuje etykiety.

Jakie tu trzeba definiować wyzwalacze i jakie stany czy:
Wariant a)
Stan
OczekiwanieNaOdczyt
Wydruk

Trigger
odczytNrFabr
potwierdzenieWydruku

Wariant b)
Stan
OczekiwanieNaOdczyt
NrFabrycznyOK
NrFabrycznyNOK
ZgloszenieBledu
Wydruk

Trigger
OdczytCzytnikiemNumeruFabr
PozytywnaWeryfikacjaNrFabr
NegatywnaWeryfikacjaNrFabrycznego
PotwierdzenieWydruku

PYTANIE 3
Co ma byc kontrolerem w do którego metod będzie się odwoływała maszyna stanów podczas zmian stanów w architekturze WPF MVVM. Czy to ma byc ViewModel czy robic jakis obiekt kontrolera dedykowany a ViewModel bedzie tylko bindowal dane z tego modelu kontrolera? Czyli z najniższego listingu co ma implementować interfejs 'IKontroleraKolowrotka'?

Poniżej taki testowy kod maszyny stanów dla kołowrotka, tylko zastąpiłem odwolywanie się do kontrolera - zwyklym tekstem. (ale nizej z właściwym podejsciem)

public class KolowrotekModel
    {
        readonly string infoZablokowane = "Wrzuć monetę aby przejść";
        readonly string infoOdblokowane = "Możesz przejść";
        readonly string infoZwrotMonety = "Już opłacone, odbierz nadpłatę";
        readonly string infoAlarm = "Hola hola amigo, najpierw opłać";

        public string KomunikatPoAkcji { get; private set; }

        internal Stan stan;
        private List<PrzejscieDoNowegoStanu> przejsciaStanow;

        public KolowrotekModel()
        {
            stan = Stan.Zablokowany;
            KomunikatPoAkcji = infoZablokowane;
            przejsciaStanow = new List<PrzejscieDoNowegoStanu>();

            DodajNowePrzejscieDoNowegoStanow(Stan.Zablokowany, Zdarzenie.Moneta,   Stan.Odblokowany, infoOdblokowane);
            DodajNowePrzejscieDoNowegoStanow(Stan.Zablokowany, Zdarzenie.Przejscie,Stan.Zablokowany, infoAlarm);
            DodajNowePrzejscieDoNowegoStanow(Stan.Odblokowany, Zdarzenie.Moneta,   Stan.Odblokowany, infoZwrotMonety);
            DodajNowePrzejscieDoNowegoStanow(Stan.Odblokowany, Zdarzenie.Przejscie,Stan.Zablokowany, infoZablokowane);
        }

        private void DodajNowePrzejscieDoNowegoStanow(Stan wejscie, Zdarzenie e, Stan wyjscie, string komunikat)
        {
            przejsciaStanow.Add(new PrzejscieDoNowegoStanu(wejscie,  e, wyjscie, komunikat));
        }

        public void HandleEvent(Zdarzenie e)
        {
            PrzejscieDoNowegoStanu zmianaStanu = przejsciaStanow
                .Where(o => o.StanWejsciowy == stan && e == o.Wyzwalacz)
                .DefaultIfEmpty(new PrzejscieDoNowegoStanu())
                .Single();

            stan = zmianaStanu.StanWyjscowy;
            KomunikatPoAkcji = zmianaStanu.KomunikatPoZmianieStanu;
        }

        private class PrzejscieDoNowegoStanu
        {
            public Stan StanWejsciowy;
            public Zdarzenie Wyzwalacz;
            public Stan StanWyjscowy;
            public string KomunikatPoZmianieStanu;

            public PrzejscieDoNowegoStanu()
            {
                StanWejsciowy = Stan.Zablokowany;
                Wyzwalacz = Zdarzenie.Przejscie;
                StanWyjscowy = Stan.Zablokowany;
                KomunikatPoZmianieStanu = "Ups Coś poszło nie tak";
            }

            public PrzejscieDoNowegoStanu(Stan stanWejsciowy, Zdarzenie wyzwalaczZdarzenia, Stan stanWyjscowy, string komunikatPoZmianieStanu)
            {
                StanWejsciowy = stanWejsciowy;
                Wyzwalacz = wyzwalaczZdarzenia;
                StanWyjscowy = stanWyjscowy;
                KomunikatPoZmianieStanu = komunikatPoZmianieStanu;
            }
        }
    }
    
public  class MainWindowViewModel : INotifyPropertyChanged 
    {
        
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged(string name)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(name));
        }


        private KolowrotekModel model;
        public MainWindowViewModel()
        {
            model = new KolowrotekModel();
        }

        public string StanKolowrotka { get { return model.stan.ToString(); } }

        public string KomunikatWyjsciowy { get { return model.KomunikatPoAkcji; } }


        private ICommand wrzucenieMonety;
        public ICommand WrzucenieMonety
        {
            get 
            {
                if (wrzucenieMonety==null)
                    wrzucenieMonety = new RelayCommand(ObslugaWrzuceniaMonety);
                return wrzucenieMonety; 
            }
        }
        private void ObslugaWrzuceniaMonety(object o)
        {
            model.HandleEvent(Zdarzenie.Moneta);
            OnPropertyChanged(nameof(StanKolowrotka));
            OnPropertyChanged(nameof(KomunikatWyjsciowy));
        }

        private ICommand przejscie;
        public ICommand Przejscie
        {
            get
            {
                if (przejscie==null)
                    przejscie = new RelayCommand(ObslugaPrzejscia);
                return przejscie;
            }
        }
        private void ObslugaPrzejscia(object o)
        {
            model.HandleEvent(Zdarzenie.Przejscie);
            OnPropertyChanged(nameof(StanKolowrotka));
            OnPropertyChanged(nameof(KomunikatWyjsciowy));
        }
    }

Tutaj cos z odwolywaniem sie do zachowań IKontroleraKolowrotka

public interface IKontrolerKolowrotka
    {
        event PropertyChangedEventHandler PropertyChanged;

        void Odblokowanie();
        void Alarm();
        void ZwrotMonety();
        void Zablokowanie();

    }

    public class KolowrotekModel
    {
        internal Stan stan = Stan.Zablokowany;
        private List<ZmianaStanu> przejsciaStanow = new List<ZmianaStanu>();
        private delegate void Akcja();

        public KolowrotekModel(IKontrolerKolowrotka kontrolerKolowrotka)
        {
            Akcja odblokowanie = new Akcja(kontrolerKolowrotka.Odblokowanie);
            Akcja alarm = new Akcja(kontrolerKolowrotka.Alarm);
            Akcja zwrot = new Akcja(kontrolerKolowrotka.ZwrotMonety);
            Akcja zablokowanie = new Akcja(kontrolerKolowrotka.Zablokowanie);

            DodajZmianeStanow(Stan.Zablokowany, Zdarzenie.Moneta,   Stan.Odblokowany, odblokowanie);
            DodajZmianeStanow(Stan.Zablokowany, Zdarzenie.Przejscie,Stan.Zablokowany, alarm);
            DodajZmianeStanow(Stan.Odblokowany, Zdarzenie.Moneta,   Stan.Odblokowany, zwrot);
            DodajZmianeStanow(Stan.Odblokowany, Zdarzenie.Przejscie,Stan.Zablokowany, zablokowanie);
        }

       //(....)
       }