State Machine pattern – et anti-pattern?

by Jesper august 19, 2009 15:09

Jeg arbejder en del med State Machine (SM) i et lille hobby projekt, og jeg har virkelig slået knuder på mig selv for at forstå og adapterer det designpattern. Når man først har haft en SM med 10-15 states oppe at køre, bliver man helt høj over hvor fedt det virker (ahmm høj over kode…). Der er bare et par ting der nager mig ved det pattern, og det rejser spørgsmålet:

Kan man virkelig bruge et pattern der strider mod Uncle Bobs principper?

Hvis du er lidt uklar på hvad SM er, burde det fremgå af følgende simple eksempel:

public class SwitchContext
{
    public SwitchContext()
    {
        this.CurrentState = new OffState();
    }
    public IState CurrentState
    {
        get;
        private set;
    }

    public void Switch()
    {
        this.CurrentState = this.CurrentState.DoWork();
    }
}

public interface IState
{
    IState DoWork();
}

public class OnState : IState
{
    public IState DoWork()
    {
        // Sluk for lyset
        return new OffState();
    }
}

public class OffState : IState
{
    public IState DoWork()
    {
        // Tænd for lyset
        return new OnState();
    }
}

Et system der gør det ud for en kontakt. Den kan kun have to tilstande: Tændt eller slukket (OnState / OffState). Vores SM, har kun en metode: “Påvirk kontakt” (Switch), som kalder interfacemetoden: DoWork.

Hvor er det så jeg har mine betænkeligheder, for det ser jo rimeligt harmløst ud?

Problemet er primært koblingen af de to IState klasser. De kan hver i sær ikke eksistere alene (kan ikke engang kompilerer), og ville i givet fald miste deres funktion – ultimativ hård kobling. Det fede ved den model, er at konteksten får ændret klassen dynamisk, ud fra hvilken tilstand den befinder sig i. Kan man gøre noget for at bibeholde den fleksibilitet, og på samme tid fjerne den hårde kobling?

Man kan omvende kontrollen af objekterne (IoC), så det er konteksten (SM) der deffinerer transformationen.

public class SwitchContext
{
    public SwitchContext()
    {
        _onState.SetDestination(_offState);
        _offState.SetDestination(_onState);
        this.CurrentState = _offState;
    }

    private State _onState = new OnState();
    private State _offState = new OffState();

    public State CurrentState{ get; private set; }

    public void Switch()
    {
        this.CurrentState = this.CurrentState.DoWork();
    }
}

public abstract class State
{
    public abstract State DoWork();

    protected State _destination;
    public void SetDestination(State destinationState)
    {
        _destination = destinationState;
    }
}

public class OnState : State
{
    public override State DoWork()
    {
        // Sluk lyset
        return _destination;
    }
}

public class OffState : State
{
    public override State DoWork()
    {
        // Tænd lyset
        return _destination;
    }
}

Så har vi fjernet den hårde kobling mellem de forskellige states, og kan nu implementerer fuldstændigt uafhængigt. Eneste ulempe er at, nu har vi koblet konteksten hårdt med de enkelte states. Ja vi er faktisk afhængige af at de bliver initialiceret i en bestemt rækkefølge. Det er måske ok, når nu konteksten er modulet, og dermed den klasse der står for kompositionen, men vi har stadig den samme “arkitekt hovedpine”.

Hvilket af de to onder bør man foretrække, og kommer det måske mere an på opgaven end på designet?

Code On…

Tags:

Code Smell

Kommentarer

19-08-2009 21:55:57 #

Kristian Erbou

Der er, som jeg ser det, i det første eksempel ikke lavet en StateMachine, men i stedet implementeret et Strategy pattern. Den er hårdt koblet i det andet eksempel, men læner sig stadig op af samme mønster... Er jeg helt galt på den?

martinfowler.com/.../Intro.html

Kristian Erbou Denmark

19-08-2009 23:19:01 #

Jesper

@KBE> Det er et meget simpelt SM eksempel, og det har ligheder med strategi. Strategi-pattern er deffineret ved en familie af algoritmer, der er indkapslet i et interface, og kan bruges fuldstændigt ligestillet.
Lad os sige du har en konsol-klasse, der kan udlæse tekst. Man kan så have en 'Linieskift'-metode, som f.eks. kan udskiftes med en HtmlLinieskiftStrategi, eller en JavascriptLinieskiftStrategi. Der vil dog stadig være tale om at man udfører et linieskift i konsol-klassen.
SM indkapsler en state, der ændre klassens virkemåde i takt med at staten ændres. Knappen går fra at være en 'sluk'-knap, til at være en 'tænd'-knap i ovenstående eksempel - altså kommer det til at virke som om klassen forandre sig.
Men de to mønstre er ganske rigtigt i famile.

Jesper Denmark

Kommentarerne er lukkede

Powered by BlogEngine.NET 1.5.0.7
Theme by Mads Kristensen | Modified by Mooglegiant

About

Mit navn er Jesper Jensen, og jeg arbejder til dagligt som web-udvikler hos DGI, hvor mit speciale er klientside applikationer. Før det var jeg nogle år i robotbranchen, hvor jeg arbejdede med 3D simulering og system koordinering. Jeg elsker webudvikling, og specielt JavaScript har min interesse. Jeg har blogget om mine oplevelser med udvikling siden 2004

Calendar

<<  september 2010  >>
mationtofr
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar

RecentComments

Comment RSS