Code Review s Architektem, Round #3 - State Machine rubrika: Programování: .Net

2 Jaroslav Urban
položil/-a 24.3.2014

Zdravím,
a je tu další bonbónek z praxe. Řešíme stavovou mašinu pro připojení k zařízení přes serial connection. Dostali jsme se do stavu, kdy máme dvě implementace a ani jedno není akceptováno druhou stranou. Nu což, tak se chci zeptat, co si o tom myslíte.

Scenář
Jelikož potřebujeme automatické obnovení spojení při krátkodobém výpadku, tak chceme použít stavovou mašinu. Obě implementace používají stejné stavy a taktéž interface definující rozhraní stavové mašiny, viz kód níže.
Jde o async zpracování událostí, jen abyste věděli, ale to bych zde zatím nevytahoval.

// přechodové stavy
public enum ConnectionStateType { Disconnected = 0,Connecting = 1,Reconnecting = 2,Connected = 3}
 
//hlavní interface stavové mašiny, inteface definuje metody pro vstupní události, které vyvolávají změny
public interface IConnectionStateMachine {
void Connect();
void Disconnect();
void OnConnected();
void HandleException(McaException exception);
}

Implementace A
má snahu extrahovat veškerou rozhodovací logiku mimo jednotlivé stavy, pro tento případ zde vznikl extra interface IConnectionStateChanger. Implementace tohoto rozhraní má za úkol kontrolovat, jestli je možné přejít z aktuálního stavu a v případě, že je to možné, tak přechod spustit. Viz metoda ChangeTo().
Jak vidíte níže, rozhraní stavu jen definuje jen jednu metodu Handle(), která provede přechod do cílového stavu, tedy do stavu, která implementuje interface IConnectionState. StateType property definuje, jaký to stav je.

public interface IConnectionStateChanger
{
    IConnectionState Current { get; }
    void ChangeTo(LifetimeStateType state);
}
 
public interface IConnectionState {
    void Handle();
    LifetimeStateType StateType { get; }
}
// ukázka disconnected stavu pro implementaci A
[CanGoTo(LifetimeStateType.Connecting)]
public class DisconnectedState : IConnectionState
{
    private readonly IReconnectionController m_ReconnectionController;
    private readonly IMCBConnectionManager m_McbConnectionManager;
    public DisconnectedState(IReconnectionController reconnectionController, IMCBConnectionManager mcbConnectionManager)
    {
        if (reconnectionController == null) throw new ArgumentNullException("reconnectionController");
        if (mcbConnectionManager == null) throw new ArgumentNullException("mcbConnectionManager");
        m_ReconnectionController = reconnectionController;
        m_McbConnectionManager = mcbConnectionManager;
    }
    public void Handle()
    {
        m_McbConnectionManager.Disconnect();
        m_ReconnectionController.Stop(); // call it every time even it is conditional and the call is depending on previous state
    }
    public LifetimeStateType StateType { get { return LifetimeStateType.Disconnected; } }
}

Pros&Cons

  • jednoduché implementace stavů
  • jednotlive stavy nemají potuchy o dalších stavech.
  • rozhodovací logika je více místech (ConnectionStateChanger, ConnectionStateMachine) to komplikuje případné přidání dalšího stavu, či správu kódu při změnách. Velkou pravděpodobnost použití SWITCHů, či větvených IFů. [hlavní argument protistrany]
  • potřeba vstupních parametrů pro IConnectionState.Handle() metodu při složitější stavové mašině. Nevím jestli je to známka špatného návrhu,ale viděl jsem implementaci s více stavy, které tyto argumenty používala. Důsledkem bylo, že byly použity jen v některých stavech a tak docházelo ke zbytečnému předávání dat, vzhledem k tomu, že se musel zachovat interface pro všechny stavy.

Implementace B
má snahu věškerou odpovědnost dát jednotlivým stavům. Stavy nejenže provádějí požadované akce nutné pro přechod, ale taktéž pomocí události oznamují změnu stavu.

public interface ILifetimeState {
    event Action<LifetimeStateType> StateChanged;
    LifetimeStateType LifetimeState { get; }
    void Connect();
    void Disconnect();
    void OnConnected();
    McaException ProcessException(McaException exception);
}
 
// ukázka disconnected stavu pro implementaci B
public class DisconnectedLifetimeState : LifetimeStateBase, ILifetimeState
{
    public DisconnectedLifetimeState(IReconnectionController reconnectionController, IMCBConnectionManager mcbConnectionManager)
        : base(reconnectionController, mcbConnectionManager) {}
 
    public LifetimeStateType LifetimeState { get { return LifetimeStateType.Disconnected; }}
 
    public void Connect() {
        RaiseStateChanged(LifetimeStateType.Connecting);
        ReconnectionController.Restart();
    }
    public void Disconnect() { } // případné volání se ignoruje
 
    public void OnConnected() {
                // provede se bez nutnosti změnit stav
        ReconnectionController.Stop();
        McbConnectionManager.Disconnect();
    }
        //zbytek metod implementuje básová třída
}

Pros&Cons

  • rozhodovací logika je vždy ve stavech, tedy na jednom místě
  • prázdné metody pro některé volané metody [hlavní argument protistrany]
  • rozšíření stavů nutně znamená změnu interfacu ILifetimeState a změnu v dalších stavech
  • bobnatění metod interfacu při větším množství stavů, tedy vstupů

A otázka:
Pro jakou implementaci byste se rozhodli a proč? Případně, navrhli byste jinou?

V případě nejasností se hlaste, pokusím se případně přidat nějaký kód, či vysvětlení. Děkuji za vaše případné příspěvky. JU

Komentáře

odkaz
2 jirkapenzes
odpověděl/-a 24.3.2014

Šel bych cestou implementací B. Stavy tak budou dobře izolované a budou zapouzdřovat svou logiku. Může to přinést stejné výhody, jako implementace A.

Ad tvoje proti argumenty:
- prázdné metody pro některé volané metody [hlavní argument protistrany]
Lze řešit cestou abstraktní třídou.

- rozšíření stavů nutně znamená změnu interfacu ILifetimeState a změnu v dalších stavech
Lze řešit vytvořením hierarchií stavů (+point možno využívat polymorfismu)

- bobnatění metod interfacu při větším množství stavů, tedy vstupů
Pokud něco takového vznikne, tak jsou pravděpodobně ty stavy špatně navržené :)

Komentáře

  • Jaroslav Urban : Díky Jirko za komentář. Abstraktní třída, viz komentář u Cpt.Nemo. Můžeš prosím trochu rozvést tu myšlenku hirearchie stavů? Ohledně bobtnání, máš nějaké zkušenosti ohledně nějakých stavových mašin, které obsahovali více stavů. Mě by zajímalo jestli je tu nějaká hranice, při které by měl autor zpozornit a zapřemýšlet o redesignu. Ono je to hodně asi subjektivní, ale třeba tu má někdo zkušenosti s použitím nějaké složitější stavové mašiny. 24.3.2014

Pro zobrazení všech 3 odpovědí se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.