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…