State

Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.



Usage in JavaScript:
medium low


Summary


The State pattern provides state-specific logic to a limited set of objects in which each object represents a particular state. This is best explained with an example.

Say a customer places an online order for a TV. While this order is being processed it can in one of many states: New, Approved, Packed, Pending, Hold, Shipping, Completed, or Canceled. If all goes well the sequence will be New, Approved, Packed, Shipped, and Completed. However, at any point something unpredictable may happen, such as no inventory, breakage, or customer cancelation. If that happens the order needs to be handled appropriately.

Applying the State pattern to this scenario will provide you with 8 State objects, each with its own set of properties (state) and methods (i.e. the rules of acceptable state transitions). State machines are often implemented using the State pattern. These state machines simply have their State objects swapped out with another one when a state transition takes place.

Two other examples where the State pattern is useful include: vending machines that dispense products when a correct combination of coins is entered, and elevator logic which moves riders up or down depending on certain complex rules that attempt to minimize wait and ride times.


Diagram



Participants


The objects participating in this pattern are:

  • Context -- In sample code: TrafficLight
    • exposes an interface that supports clients of the service
    • maintains a reference to a state object that defines the current state
    • allows State objects to change its current state to a different state
  • State -- In sample code: Red, Yellow, Green
    • encapsulates the state values and associated behavior of the state

JavaScript Code


Our example is a traffic light (i.e. TrafficLight object) with 3 different states: Red, Yellow and Green, each with its own set of rules. The rules go like this: Say the traffic light is Red. After a delay the Red state changes to the Green state. Then, after another delay, the Green state changes to the Yellow state. After a very brief delay the Yellow state is changed to Red. And on and on.

It is important to note that it is the State object that determines the transition to the next state. And it is also the State object that changes the current state in the TrafficLight -- not the TrafficLight itself.

For demonstration purposes, a built-in counter limits the number of state changes, or else the program would run indefinitely.

The log function is a helper which collects and displays results.


var TrafficLight = function () {

    var count = 0;
    var currentState = new Red(this);

    this.change = function (state) {
        // limits number of changes
        if (count++ >= 10) return;

        currentState = state;
        currentState.go();
    };

    this.start = function () {
        currentState.go();
    };
}

var Red = function (light) {
    this.light = light;

    this.go = function () {
        log.add("Red --> for 1 minute");
        light.change(new Green(light));
    }
};

var Yellow = function (light) {
    this.light = light;

    this.go = function () {
        log.add("Yellow --> for 10 seconds");
        light.change(new Red(light));
    }
};

var Green = function (light) {
    this.light = light;

    this.go = function () {
        log.add("Green --> for 1 minute");
        light.change(new Yellow(light));
    }
};

// log helper

var log = (function () {
    var log = "";
    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { alert(log); log = ""; }
    }
})();

function run() {

    var light = new TrafficLight();
    light.start();

    log.show();
}
Run



JavaScript Optimized Code


The Namespace pattern is applied to keep the code out of the global namespace. Our namespace is named Patterns.Classic. A Revealing Module named State returns (i.e. reveals) only a single item: the TrafficLight constructor function.

All three state items Red, Yellow, and Green are kept private in TrafficLight's closure. They have full access to the enclosing function which includes change through which they change the state. So the state objects do not need an explicit reference to TrafficLight anymore, they have implicit access. The module only exposes TrafficLight, which, in turn, only exposes the start method. This is a nice example of OO encapsulation and data hiding using JavaScript patterns.

The Patterns object contains the namespace function which constructs namespaces non-destructively, that is, if a name already exists it won't overwrite it.

The log function is a helper which collects and displays results.


var Patterns = {
    namespace: function (name) {
        var parts = name.split(".");
        var ns = this;

        for (var i = 0, len = parts.length; i < len; i++) {
            ns[parts[i]] = ns[parts[i]] || {};
            ns = ns[parts[i]];
        }

        return ns;
    }
};

Patterns.namespace("Classic").State = (function () {
    var TrafficLight = function () {

        var Red = function () {
            this.go = function () {
                log.add("Red --> for 1 minute");
                change(new Green());
            }
        };

        var Yellow = function () {
            this.go = function () {
                log.add("Yellow --> for 10 seconds");
                change(new Red());
            }
        };

        var Green = function () {
            this.go = function () {
                log.add("Green --> for 1 minute");
                change(new Yellow());
            }
        };

        var count = 0;
        var currentState = new Red(this);

        var change = function (state) {
            // limits number of changes
            if (count++ >= 10) return;

            currentState = state;
            currentState.go();
        };

        this.start = function () {
            currentState.go();
        };
    }

    return {
        TrafficLight: TrafficLight
    };

})();

// log helper
var log = (function () {
    var log = "";
    return {
        add: function (msg) { log += msg + "\n"; },
        show: function () { alert(log); log = ""; }
    }
})();

function run() {

    var light = new Patterns.Classic.State.TrafficLight();
    light.start();

    log.show();
}
Run



  Observer
Strategy