Created
March 24, 2015 21:11
-
-
Save amacdougall/4d3fcce1f893ac610bd0 to your computer and use it in GitHub Desktop.
State machine examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// simple state machine using a state variable | |
/* States can be anything -- strings, integers, whatever. In C examples, you'll | |
* see integers, because they take up less space in memory. In Ruby, they'd be | |
* symbols; in Java, they'd be Enumerations or something. In JavaScript, it's | |
* generally wise to make them strings, for easy debugging. | |
* | |
* These example states are for an imaginary ship. | |
*/ | |
var ANCHORED = "anchored"; | |
var ADRIFT = "adrift"; | |
var ACCELERATING = "accelerating"; | |
var CRUISING = "cruising"; | |
var BRAKING = "braking"; | |
class Ship { | |
constructor() { | |
this.state = ANCHORED; // we never refer to the state's actual value | |
this.init(); | |
} | |
init() { | |
var self = this; | |
// note that the state change emitter can handle state changes | |
self.on(Event.STATE_CHANGED, function(newState) { | |
if (newState === CRUISING) { | |
self.announceToPassengers("We have reached cruising speed."); | |
} | |
}); | |
// optionally, you can handle events by changing states | |
self.on(NauticalEvent.RAISE_ANCHOR, e => self.changeState(ADRIFT)); | |
// or you can change states as part of a function | |
} | |
changeState(newState) { | |
var oldState = this.state; | |
this.state = newState; | |
this.emit(Event.STATE_CHANGED, { | |
oldState: oldState, | |
newState: newState | |
}); | |
} | |
raiseAnchor() { | |
this.storeAnchorChain(); | |
this.readyInstruments(); | |
// ...etc... you know, ship stuff | |
// you can change state, emit an event, or both | |
this.emit(NauticalEvent.RAISE_ANCHOR); | |
} | |
lowerAnchor() { | |
switch (this.state) { | |
case ANCHORED: | |
throw new Error("Cannot lower anchor; already lowered."); | |
case ADRIFT: | |
actualImplementation(); | |
break; | |
case ACCELERATING: | |
case CRUISING: | |
case BRAKING: | |
throw new Error("Cannot lower anchor while under way."); | |
} | |
} | |
} | |
// if you wanted a class-based system, you'd do this: | |
class ShipState { | |
constructor(ship) { | |
this.ship = ship; | |
} | |
lowerAnchor() { | |
throw new Error("Subclasses must implement this method."); | |
} | |
} | |
class AnchoredState extends ShipState { | |
lowerAnchor() { | |
throw new Error("Cannot lower anchor; already lowered."); | |
} | |
} | |
class AdriftState extends ShipState { | |
lowerAnchor() { | |
ship.doActualStuff(); | |
} | |
} | |
/* The class-based version has a few advantages. For one thing, you can | |
* store data in the state itself and even pass it to successive states. | |
* But the big advantage is that all the behavior for each state is in the | |
* same place: here are all the anchored versions, then the adrift versions, | |
* and so on. | |
* | |
* If you just use a state variable, the logic for each state is grouped by | |
* function, not by state: here's the lowerAnchor function, which contains | |
* logic for every possible state. | |
*/ | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment