[design patterns] de state machine

Pagina: 1
Acties:

  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Voor een applicatie waar ik op dit moment mee bezig ben, heb ik een zwaar redeneer proces op een aparte thread lopen om de gui te ontlasten (anders blijft die helemaal stil staan). Daarna moet de thread ook weer contact op kunnen nemen met de gui om vragen te laten beantwoorden. Tijdens dit beantwoorden kan de gebruiker er oa voor kiezen om een andere sessie te beginnen, om een andere vraag opnieuw te beantwoorden, of om de redeneer sessie gewoon te laten stoppen. Tijdens het evalueren kunnen er fouten optreden (bv een rekenkundige fout of een database fout) en dezen moeten ook afgehandeld worden. Zoals je ziet heb je een groot scala aan mogelijkheden en dit zijn ze nog niet eens allemaal.

Ik heb 1 1/2 jaar geleden ook eens een oplossing voor gemaakt, maar ik was er eigelijk nooit tevreden over. Ik heb daar in de tussentijd veel over nagedacht hoe ik voor dit soort problemen (een probleem met een hele lading toestanden en toestandovergangen) een sluitende oplossing kon vinden.

Het ontwerp dat ik er voor heb gebruikt vond ik zelf eigelijk zeer ellegant. Ik heb op basis van een toestandsdiagram alle toestanden en alle events hiervoor geschreven vb:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class BeginEngineState implements EngineState {

    private static BeginEngineState s_instance = new BeginEngineState();

    public static BeginEngineState getInstance() {
        return s_instance;
    }

    private BeginEngineState() {
    }

    public BeginEngineState reinit(){
        return BeginEngineState.getInstance();
    }

    public RunningEngineState start() {
        return RunningEngineState.getInstance();
    }

    public void accepts(EngineStateVisitor v) {
        v.visit(this);
    }

    public String toString() {
        return "begin";
    }
}

public class StartEvent extends EngineEvent {

    public StartEvent(EngineManager manager) {
        super(manager);
    }
}


Je kan nu vanaf de starttoestand, alleen in de running toestand komen, of je kan gereinitialiseerd worden. Door op deze manier te werken, kan ik garanderen dat je nooit in een verkeerde toestand kan komen. Daarnaast heb ik een object (de wrapper om de current state heen) gemaakt dat in zich een bepaalde toestand heeft, en voor ieder toestandsovergang een methode. Hij controleert eerst of je wel in de juiste toestand zit en stelt daarna de nieuwe toestand in (en is ook nog even zo vriendelijk om wat events te versturen naar listeners zodat de gui leuk kan reageren op de events).

Java:
1
2
3
4
5
6
7
8
9
10
11
12
public void start() {
        if (!(_state instanceof BeginEngineState))
            throw new EngineStateException(BeginEngineState.getInstance(), _state);

        _state = ((BeginEngineState) _state).start();

        //stuur aan alle luisteraars het nieuwe event
        StartEvent startEvent = new StartEvent(_manager);
        for (Iterator<EngineListener> itt = _listenerList.iterator(); itt.hasNext();) {
            itt.next().start(startEvent);
        }
    }


Ik was eigelijk wel erg tevreden over dit ontwerp omdat ik nu eindelijk eens een sluitende oplossing had voor iedere toestand waarin de engine kan verkeren. Ik wist gewoon dat hiervoor wel ergens een design pattern voor moest bestaan. De 'State' design pattern lijkt hier wel een beetje op, maar deze had zich niet zozeer bezig met de toestandsovergangen, en deze doet dat wel.

Ik was gisteravond even aan het bladeren in thinking in patterns van Bruce Eckel, en zag daar ineens de State Machine design pattern staan.. en ik had zoiets van tjakkaa... dat is hem.. Ik raad dus iedereen aan om eens wat te gaan lezen over het state machine design pattern... echt een geweldige design pattern waarmee je inzicht krijgt en houdt in een systeem vol toestandsovergangen.

[ Voor 11% gewijzigd door Alarmnummer op 05-08-2003 12:02 ]


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-08 18:33
Ziet er goed uit. Voor het type software dat ik schrijf heb ik vaak statemachines nodig, en ik heb dit tot nu toe nog niet echt goed kunnen combineren met OO, je komt dan altijd weer uit op een grote switch. ( Op meerde plaatsen in je app als het ff tegenzit )

Zal eens wat meer verdiepen in deze. ( En je visitor pattern :) )
Ik was gisteravond even aan het bladeren in thinking in patterns van Bruce Eckel, en zag daar ineens de State Machine design pattern staan..
Is dat boek nou al eens af of niet ? Die Eckel is een fijne man, maar ik moet al weet ik niet hoe lang wachten op deze en Vol2 van Thinking in C++.

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • whoami
  • Registratie: December 2000
  • Laatst online: 21-08 22:54
farlane schreef op 05 August 2003 @ 11:38:
[...]


Ziet er goed uit. Voor het type software dat ik schrijf heb ik vaak statemachines nodig, en ik heb dit tot nu toe nog niet echt goed kunnen combineren met OO, je komt dan altijd weer uit op een grote switch. ( Op meerde plaatsen in je app als het ff tegenzit )
Ik weet natuurlijk het doel en design van je applicatie niet, maar als je veel gebruik moet maken van switches enzo, is het dan geen idee om het strategy design pattern te gebruiken?

https://fgheysels.github.io/


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
farlane schreef op 05 August 2003 @ 11:38:
Ziet er goed uit. Voor het type software dat ik schrijf heb ik vaak statemachines nodig, en ik heb dit tot nu toe nog niet echt goed kunnen combineren met OO, je komt dan altijd weer uit op een grote switch. ( Op meerde plaatsen in je app als het ff tegenzit )
Klinkt eng :P Een van de hoofd oo regels is 'encapsulate what changes'. Aangezien die state het gene is dat veranderd, zou ik dat onderbrengen in een object. En zo gauw je een object te pakken hebt, kan je er ook polymorfisme en andere leuke oo grappen.
Is dat boek nou al eens af of niet ? Die Eckel is een fijne man, maar ik moet al weet ik niet hoe lang wachten op deze en Vol2 van Thinking in C++.
Het boek is nog niet af. Hij spring nog enorm van de hak op de tak dus het leest niet altijd even makkelijk. En verder geeft hij ook te veel aandacht aan problemen waar al lang een oplossing voor beschikbaar is, zoals bv zijn eigen versie van unit-testen. Misschien dat dit komt omdat hij al zo lang bezig is met dat boek, maar je begint regelmatig snel door te bladeren.

Als ik je nog iets mag aanraden, dan raad ik je deze aan:
Design Patterns Explained: A New Perspective on Object-Oriented Design Dit boek is imho een stuk minder droog dan het GoF boek, en je gaat echt op een andere manier tegen oo design aankijken. Echt een dikke aanrader.

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-08 18:33
whoami schreef op 05 August 2003 @ 11:44:
[...]
Ik weet natuurlijk het doel en design van je applicatie niet, maar als je veel gebruik moet maken van switches enzo, is het dan geen idee om het strategy design pattern te gebruiken?
Die kan idd worden toegepast waar er 'geswitched' moet worden tussen veschillende 'bewerkingen'.

Maar de meest vervelende ( lees langste ) switches treden iha op bij statemachines. Een switch met 20 states ( labels ) is al best vervelend om te lezen, laat staan onderhouden. :)

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-08 18:33
Is het ook. :) Onderhouden wordt na verloop van tijd een ramp omdat een nieuwe state altijd betekent dat je op al die 'switch' plekken je code doormoet.
Het boek is nog niet af. Hij spring nog enorm van de hak op de tak dus het leest niet altijd even makkelijk.
Volgens mij is die man met veel te veel dingen tegelijk bezig :)
Als ik je nog iets mag aanraden, dan raad ik je deze aan: ....
Thx, ga er direct ff naartoe :)
BTW, heb je die zelf ook ?

[edit]
Spelvauten

[ Voor 7% gewijzigd door farlane op 05-08-2003 12:11 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
farlane schreef op 05 August 2003 @ 11:58:
Is het ook. :) Onderhouden wordt na verloop van tijd een ramp omdat een nieuwe state altijd betekend dat je op al die 'switch' plekken je code doormoet.
' do it once.. and once only' :P en 'replace conditionals by polymorfism' - martin fowler - refactoring.
Thx, ga er direct ff naartoe :)
BTW, heb je die zelf ook ?
Ik heb hem zelf ook...

[ Voor 12% gewijzigd door Alarmnummer op 05-08-2003 12:06 ]


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Ik heb nog even wat aanpassingen gemaakt:

Java:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
public abstract class EngineState {

    public final static EngineState BEGIN = new BeginEngineState();
    public final static EngineState FINISHED = new FinishedEngineState();
    public final static EngineState STOPPED = new StoppedEngineState();
    public final static EngineState READY_TO_RUN = new ReadyToRunEngineState();
    public final static EngineState RUNNING = new RunningEngineState();
    public final static EngineState ERROR = new ErrorEngineState();
    public final static EngineState WAITING_FOR_ANSWERS = new WaitingForAnswersEngineState();

    public abstract EngineState next(EngineEvent e);

    private static class BeginEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof InitEvent)
                return READY_TO_RUN;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "begin";
        }
    }

    private static class FinishedEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof AnswerChangedEvent)
                return RUNNING;

            if (e instanceof InitEvent)
                return READY_TO_RUN;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "finished";
        }
    }

    private static class ReadyToRunEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof StartEvent)
                return RUNNING;

            if (e instanceof InitEvent)
                return this;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "readyToRun";
        }
    }

    private static class StoppedEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof InitEvent)
                return READY_TO_RUN;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "stopped";
        }
    }

    private static class RunningEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof AnswerFoundInMemoryEvent)
                return this;

            if (e instanceof ErrorEvent)
                return ERROR;

            if (e instanceof FinishEvent)
                return FINISHED;

            if (e instanceof AnswerNeededEvent)
                return WAITING_FOR_ANSWERS;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "running";
        }
    }

    private static class ErrorEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof InitEvent)
                return READY_TO_RUN;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "error";
        }
    }

    private static class WaitingForAnswersEngineState extends EngineState {
        public EngineState next(EngineEvent e) {
            Assert.assertNotNull(e);

            if (e instanceof InitEvent)
                return READY_TO_RUN;

            if (e instanceof StopEvent)
                return STOPPED;

            if (e instanceof AnswerAvailableEvent)
                return RUNNING;

            if (e instanceof AnswerChangedEvent)
                return RUNNING;

            throw new EngineStateException(this,e);
        }
        public String toString(){
            return "waitingForAnswer";
        }
    }
}

public class EngineStateMachine {

    private EngineState _state = EngineState.BEGIN;
    private List<EngineListener> _listenerList = new LinkedList<EngineListener>();

    public EngineStateMachine() {
    }

    public void addListener(EngineListener l) {
        Assert.assertNotNull(l);
        _listenerList.add(l);
    }

    public void next(EngineEvent e){
        Assert.assertNotNull(e);
        System.out.println("current-state: "+_state);
        System.out.println("e: "+e.getClass());
        System.out.println();
        _state = _state.next(e);
        e.accepts(new EventDispatcher());
    }

    private class EventDispatcher implements EngineEventVisitor{

        public void visit(AnswerAvailableEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().answerAvailable(e);
        }

        public void visit(AnswerChangedEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().answerChanged(e);
        }

        public void visit(AnswerFoundInMemoryEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().answerFoundInMemory(e);
        }

        public void visit(AnswerNeededEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().answerNeeded(e);
        }

        public void visit(ErrorEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().errorEncountered(e);
        }

        public void visit(FinishEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().finish(e);
        }

        public void visit(InitEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().init(e);
        }

        public void visit(StartEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().start(e);
        }

        public void visit(StopEvent e) {
            for(Iterator<EngineListener> itt = _listenerList.iterator();itt.hasNext();)
                itt.next().stopped(e);
        }
    }


}


Die visitor gaat er denk ik nog weer uit en breng het event versturen onder bij de events zelf.

[ Voor 6% gewijzigd door Alarmnummer op 06-08-2003 10:11 ]


  • alienfruit
  • Registratie: Maart 2003
  • Laatst online: 07:56

alienfruit

the alien you never expected

Alarmnummer, mag ik jou vragen waar GoF voor staat? Ik kan niet zo snel uit deze afkorting herleiden wat de (mogelijke) titel van het boek moet zijn.

  • gorgi_19
  • Registratie: Mei 2002
  • Laatst online: 20-08 11:40

gorgi_19

Kruimeltjes zijn weer op :9

alienfruit schreef op 06 augustus 2003 @ 14:14:
Alarmnummer, mag ik jou vragen waar GoF voor staat? Ik kan niet zo snel uit deze afkorting herleiden wat de (mogelijke) titel van het boek moet zijn.
http://www.amazon.com/exe...7776996-4699329?vi=glance

GoF = Gang of Four, waarmee bedoeld wordt de 4 auteurs van het boek Design Patterns.

[ Voor 12% gewijzigd door gorgi_19 op 06-08-2003 14:15 ]

Digitaal onderwijsmateriaal, leermateriaal voor hbo


  • alienfruit
  • Registratie: Maart 2003
  • Laatst online: 07:56

alienfruit

the alien you never expected

Thanks. Gorgi_19 ik ging er vanuit dat het een titel van een boek was ipv zoiets dergelijks. Maar dit boek Design Patterns heb ik wel. :)

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-08 18:33
Was zelf even aan het stoeien met dit verhaal, maar op een of andere manier staat die rtti me tegen.

Bovendien is dit eigenlijk weer een verborgen switch.

Vraag me af of er geen elegante methode is om dit zonder te doen ?

[edit]

Nog een vraagje ... waar bevinden zich bij jou de voorwaarden of een state in een bepaalde andere state mag overgaan ? Zou je die in de state objecten zelf onderbrengen ( wat misschien wel weer betekent dat de coupling tussen de state objecten wordt vergroot ), of gebruik je daarvoor een ander object ?

[ Voor 31% gewijzigd door farlane op 06-08-2003 20:24 ]

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
farlane schreef op 06 augustus 2003 @ 20:20:
[...]


Was zelf even aan het stoeien met dit verhaal, maar op een of andere manier staat die rtti me tegen.
Je bedoelt die visitor?
Bovendien is dit eigenlijk weer een verborgen switch.
Switches zijn niet zo erg. Als je bij een switch compile time kan garanderen dat je voor iedere optie ook een case hebt, dan zie ik eerlijk gezegd geen verschil. En ik heb er verder een enorme hekel aan om functionaliteit onder te brengen die eigelijk niet echt bij het object thuis hoort omdat die functionaliteit gegroepeerd bij een andere subsysteem had moeten zitten. Ik vind het dus vervelender dat functionaliteit ligt verspreid over het systeem dan om een switch te gebruiken. Ik gebruik dus veel visitors om functionaliteit te groeperen binnen 1 object, zoals dit gebeurt bij die eventverstuurder.
Vraag me af of er geen elegante methode is om dit zonder te doen ?
Er zijn er genoeg :) Als java nou eens echte multidispatch had, dan had je niet hoeven te emmeren met een visitor en verder kan je het event versturen ook onderbrengen bij de event. In dit geval zou dat niet erg zijn, omdat events en listeners bij elkaar horende functionaliteit is, en de cohesie van je object dan ook hoog blijft.
Nog een vraagje ... waar bevinden zich bij jou de voorwaarden of een state in een bepaalde andere state mag overgaan ? Zou je die in de state objecten zelf onderbrengen ( wat misschien wel weer betekent dat de coupling tussen de state objecten wordt vergroot ), of gebruik je daarvoor een ander object ?
Een state mag in principe altijd in een andere state overgaan, als het event dat de overgang tot stand brengt, ook correct is. Als je bv kijkt bij de FinishedState, dan kan je daar nooit een StopEvent op los laten, omdat die state dat niet aankan (je krijgt dan een StateException). Op deze manier kan ik garanderen dat ik nooit een verkeerde toestandsovergang kan krijgen.

  • farlane
  • Registratie: Maart 2000
  • Laatst online: 21-08 18:33
Misschien ook nog wel een leuk artikel over de state machine:

http://liemur.co.uk/Articles/FiniteStateMachine.html

Somniferous whisperings of scarlet fields. Sleep calling me and in my dreams i wander. My reality is abandoned (I traverse afar). Not a care if I never everwake.


  • Alarmnummer
  • Registratie: Juli 2001
  • Laatst online: 09-07-2024
Ik zal het artikel even doorlezen en ik heb de site meteen toegevoegd aan mijn 'niet verliezen' lijst, thanx.
Pagina: 1