Skip to content

Instantly share code, notes, and snippets.

@amacdougall
Created March 24, 2015 21:11
Show Gist options
  • Save amacdougall/4d3fcce1f893ac610bd0 to your computer and use it in GitHub Desktop.
Save amacdougall/4d3fcce1f893ac610bd0 to your computer and use it in GitHub Desktop.
State machine examples
// 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