The State Pattern with Vanilla JavaScript

Patrick Ackerman
5 min readFeb 10, 2017

--

The third in a series of JavaScript oriented design pattern articles

Now that we’ve augmented our MVC architectural pattern with the Observer design pattern, let’s build upon our existing code to implement the State pattern.

The State Pattern

What is the State Pattern? It is different than the concept of a State Machine, but can be used to build one. Instead of having enumerated values representing different states, our states are abstracted into objects.

State Pattern UML Diagram (Source: Wikipedia)

We have a container state, which contains our potential states, and each state has the responsibility of shifting to the appropriate state. In other words, we have a reference to the current state in the container, but when the state objects transition method is called, they are responsible for updating that state. Each state knows about its potential successor states, but the container is only aware of the initial state (at least it will be in our implementation. We could pass an initial state in as part of the constructor in which case it would be completely interchangeable).

Our implementation will look something like this.

It sounds confusing, but as we start to implement it, things will start making sense.

The State Container

We need an object that exposes the state to objects that may need to reference it, as well as allow the state to be manipulated. In order to accomplish this, our code will have to store a reference to the current state, and have a method for changing the state:

function HeadingState(){
var self = this;
this.state = new HelloState(self);
this.changeState = function(){
self.state.next();
}
this.getValue = function(){
return self.state.value
}
}

We still need to make HelloState , but you can see that we inject the parent container into our concrete state objects as part of their constructors. This will allow us to pass around the container, and change it’s state reference as we cycle through the available states. All of our states will implement a next method in order to move from one state to the next, while we can call changeState from the context of wherever HeadingState is instantiated. We also have a method for retrieving the relevant part of the state that we need (getValue()).

Now our states themselves:

function HelloState(container){
var self = this;
this.container = container;
this.value = 'Hello';
container.state = this;
this.next = function(){
return new WorldState(self.container);
}
}
function WorldState(container){
var self = this;
this.container = container;
this.value = 'World';
container.state = this;
this.next = function(){
return new HelloState(self.container);
}
}

Whenever we switch states, we instantiate a new state with the same container passed around. As the constructor runs, the container’s state is set to the current state object. Each state has an appropriate value (‘Hello’ or 'World'), which we can then access from our program. We just need to update our model with a changeHeading method:

function Model(){
var self = this;
var state = new HeadingState();
var heading = state.getValue();
this.observers = [];
this.registerObserver = function(observer){
self.observers.push(observer);
}
this.notifyAll = function(){
self.observers.forEach(function(observer){
observer.update(self);
})
}
//add changeHeading method to toggle state;
this.changeHeading = function(){
console.log('change heading');
state.changeState();
self.heading = state.getValue();
}
Object.defineProperty(this,"heading",{
get: function() { return heading; },
set: function(value) {
heading = value;
this.notifyAll();
}
});
}

Notice that we assign self.heading instead of tow the scoped variable heading so that we can still take advantage of the overridden set method defined in Object.defineProperty. We should also change our controller’s click handler now, so that instead of setting the heading value, it calls the model’s changeHeading method.

Now we can toggle between Hello and World to our heart’s content.

So What?

So why do we want to use the State pattern, over a simple toggle method? Honestly, we wouldn’t in a non-contrived situation of this same scope. Like much of what we’ve done so far, this is overkill for such a trivial program. However, I think its easier to understand the practical implementations of patterns when in a trivial situation. There’s less overhead for us to worry about, and we can focus on the relevant code.

In a real world application, the State pattern would be useful for adding new states that we might not have conceptualized yet, potentially more easily than with a switch case. Each state is encapsulated and can have its own unique internal operations.

It’s important to remember, design patterns are descriptive, not prescriptive. They are a way to solve similar problems that many people have recognized and adapted, but they are not necessarily the definitive way to solve a problem. Sometimes, you may end up confusing yourself or others, and add unnecessary complexity. For instance, the functionality we implemented with our MVC pattern, our Observer pattern, and our State pattern took about 100 lines of code. We could accomplish the same functionality with a simple anonymous event listener function in one line. However, if we were building something more complex (like an in-browser calculator or single page web application), these patterns help to organize code and maintain consistency across our program. In a real-world situation, patterns should be implemented as necessary and for concrete reasons, not just because you want to use them. (That is not to say that you can’t plan to use patterns during the design phase of your implementation as you recognize that they will server a purpose).

The End… ?

We implemented only two of the many GOF patterns, and they were both behavioral patterns (the other two major categories being structural and creational). I’m hesitant to cram any more patterns into the example we’ve been gradually building, but if I think of any clever ways to do so or to present them in other ways, I may make some future entries.

In the meantime, maybe think about whether or not you should use patterns or even OOP at all:

--

--