This is the 13th day of my participation in the August More Text Challenge

The state pattern is an unusually good pattern, and it may be the best way to solve certain requirements scenarios. The key of state mode is to distinguish the internal state of things. The change of internal state of things often brings about the change of behavior of things.

Recognize state patterns for the first time

Let’s imagine a situation where there is an electric light with only one switch on it. When the light is on, press the switch at this time, the electric light will switch to off state; Press the switch again and the light will be turned on again. The same switch button behaves differently in different states.

First example: the light program

First, the realization of lamp program without state mode is given:

var Light = function() {
  this.state = 'off' // The initial state of the lamp is off
  this.button = null // Light switch button
}
Copy the code

Next we define the light.prototype. init method, which is responsible for creating a real button node in the page. Assume that the button is the light switch button. When the button’s onclick event is triggered, the light switch is pressed.

Light.prototype.init = function () {
  var button = document.createElement('button'),
    self = this;
  button.innerHTML = 'switch';
  this.button = document.body.appendChild(button);
  this.button.onclick = function () { self.buttonWasPressed(); }};Copy the code

When the switch is pressed, the program calls the self.ButtonWasPressed method, which encapsulate all actions after the switch is pressed.

Light.prototype.buttonWasPressed = function () {
  if (this.state === 'off') {
    console.log('turn on the light');
    this.state = 'on';
  } else if (this.state === 'on') {
    console.log('off');
    this.state = 'off'; }};var light = new Light();
light.init();
Copy the code

But there isn’t just one type of electric light in the world. Many hotels have another kind of electric light, which also has a single switch, but it behaves like this: the first press to turn on the weak light, the second press to turn on the strong light, and the third press to turn off the light. The code above must now be modified to complete the manufacture of the new lamp:

Light.prototype.buttonWasPressed = function () {
  if (this.state === 'off') {
    console.log('weak light');
    this.state = 'weakLight';
  } else if (this.state === 'weakLight') {
    console.log('light');
    this.state = 'strongLight';
  } else if (this.state === 'strongLight') {
    console.log('off');
    this.state = 'off'; }};Copy the code

Now consider the disadvantages of the above program:

  • buttonWasPressesIs a violation of the open-closed principle, and the code in this method needs to be changed every time it is added or modified.
  • All of the state related behavior is encapsulated inbuttonWasPressedInside, if there are too many states, this method will be complicated.
  • The switching of states is very subtle, and it is not clear at a glance how many states the lamp has.
  • The switching relationship between states is just going tobuttonWasPressedMethod of stackingThe if and the elseStatement, adding or modifying a state may require changing several operations, which makesbuttonWasPressedMore difficult to read and maintain.

State mode improves lamp program

When we talk about encapsulation, we tend to encapsulate the behavior of an object first, not its state. In state mode, however, the opposite is true. The key of state mode is to encapsulate each state of a thing into a separate class, and the behavior related to this state is encapsulated inside this class. Therefore, when a button is pressed, it only needs to delegate the request to the current state object in the context. The state object is responsible for rendering its own behavior.

First, three state classes will be defined, which are offLightState, WeakLightState and strongLightState respectively. Each of these classes has a prototype method buttonWasPressed that represents the behavior that will occur when the button is pressed in its respective state:

// OffLightState:
var OffLightState = function (light) {
  this.light = light;
};
OffLightState.prototype.buttonWasPressed = function () {
  console.log('weak light'); // offLightState Corresponds to the behavior
  this.light.setState(this.light.weakLightState);
};

// WeakLightState:
var WeakLightState = function (light) {
  this.light = light;
};
WeakLightState.prototype.buttonWasPressed = function () {
  console.log('light'); // The corresponding behavior of weakLightState
  this.light.setState(this.light.strongLightState);
};

// StrongLightState:
var StrongLightState = function (light) {
  this.light = light;
};
StrongLightState.prototype.buttonWasPressed = function () {
  console.log('off'); // strongLightState corresponds to the behavior
  this.light.setState(this.light.offLightState); // Switch the state to offLightState
};
Copy the code

In the constructor of the Light class, we create a state object for each state class, so that we can clearly see how many states the lamp has:

var Light = function () {
  this.offLightState = new OffLightState(this);
  this.weakLightState = new WeakLightState(this);
  this.strongLightState = new StrongLightState(this);
  this.button = null;
};
Copy the code

In the event of the button button is pressed, the Context is no longer any substantive action directly, but by the self. The currState. ButtonWasPressed () will request delegated to the current state of the holding objects to perform.

Light.prototype.init = function () {
  var button = document.createElement('button'),
    self = this;
  this.button = document.body.appendChild(button);
  this.button.innerHTML = 'switch';
  this.currState = this.offLightState;
  this.button.onclick = function () { self.currState.buttonWasPressed(); }};Copy the code

Finally, a light.prototype. setState method is provided, which can be used to switch the state of a Light object.

Light.prototype.setState = function (newState) {
  this.currState = newState
}
Copy the code

In this case, when we need to add a new state to the Light object, we simply add a new state class. Assuming the light object now has a SuperStrongLightState, add the SuperStrongLightState class first:

var SuperStrongLightState = function (light) {
  this.light = light
}
SuperStrongLightState.prototype.buttonWasPressed = function () {
  console.log('off');
  this.light.setState(this.light.offLightState);
};
Copy the code

Then add a superStrongLightState object to the Light constructor:

var Light = function () {
  this.offLightState = new OffLightState(this);
  this.weakLightState = new WeakLightState(this);
  this.strongLightState = new StrongLightState(this);
  this.superStrongLightState = new SuperStrongLightState(this); // Add a superStrongLightState object
  this.button = null;
};
Copy the code

Finally, change the switching rule between status classes from StrongLightState→OffLightState to StrongLightState→SuperStrongLightState→OffLightState:

StrongLightState.prototype.buttonWasPressed = function () {
  console.log('Super bright light'); // strongLightState corresponds to the behavior
  this.light.setState(this.light.superStrongLightState);// Switch the state to offLightState
};
Copy the code

Lack of workarounds for abstract classes

As you can see, there are some common behavior methods defined in the state class that the Context will eventually delegate requests to on the state object, in this case buttonWasPressed. No matter how many state classes are added, they must implement the buttonWasPressed method.

In Java, all State classes must inherit from a State abstract superclass, although there is an option to implement the State interface if no common functionality is worth putting into the abstract superclass. The reason for doing this is partly because of the upward transition we’ve mentioned many times, and partly to ensure that all state subclasses implement the buttonWasPressed method. Unfortunately, JavaScript supports neither abstract classes nor the concept of interfaces. So be careful when using state mode. If we write a state subclass and forget to implement the buttonWasPressed method for that state subclass, we will throw an exception when the state switches. Because the Context always delegates the request to the buttonWasPressed method of the state object.

We can have the abstract method of the abstract superclass directly throw an exception that will be detected at least while the program is running:

var State = function () {}; State.prototype.buttonWasPressed =function () {
  throw new Error('Parent buttonWasPressed method must be overridden');
};
var SuperStrongLightState = function (light) {
  this.light = light;
};
SuperStrongLightState.prototype = new State(); // Inherits the abstract parent class
SuperStrongLightState.prototype.buttonWasPressed = function () {
  console.log('off');
  this.light.setState(this.light.offLightState);
};
Copy the code

Advantages and disadvantages of state patterns

advantages

  • The state pattern defines the relationship between state and behavior and encapsulates them in a class. By adding new state classes, it is easy to add new states and transitions.
  • avoidContextInfinite expansion, state switching logic is distributed in the state class, also removedContextOriginally too many conditional branches in.
  • Use objects instead of strings to record the current state, making state switching more obvious.
  • ContextRequest actions and behavior encapsulated in state classes can easily change independently of each other.

disadvantages

  • The disadvantage of the state pattern is that many state classes are defined in the system. Writing 20 state classes is tedious and adds many objects to the system.
  • Because the logic is scattered in the state class, it avoids undesirable conditional branching statements, but also causes the problem of logic dispersion, we cannot see the entire state machine logic in one place.

Performance optimization points in state mode

  • There are two options for managingstateObject creation and destruction. The first is only ifstateObjects are created when they are needed and then destroyed. The other is to create all state objects from the start and never destroy them. ifstateObjects are large, so you can use the first method to save memory by avoiding creating unnecessary objects and recycling them in a timely manner. But if state changes are frequent, it’s best to start with thosestateThe objects are created and there is no need to destroy them because they may be used again soon.
  • We provide for everyContextObjects are created as a groupstateObjects, actually thesestateObjects can be shared between each otherContextObjects can share onestateObject, which is also one of the application scenarios of the Meta pattern.

Relationship between state patterns and policy patterns

Similarities: They all have a context and some policy or state classes to which the context delegates the request.

Differences: Each strategy class in the strategy mode is equal and parallel, and there is no connection between them. Therefore, customers must be familiar with the role of these strategy classes, so that customers can actively switch algorithms at any time. However, in the state mode, the state and the behavior corresponding to the state have long been encapsulated, and the switch between the states has long been specified, and the “behavior change” happens within the state mode. These details are not necessary for the customer. This is where state patterns come in.

One last word

If this article is helpful to you, or inspired by the words, help like attention, your support is the biggest motivation I insist on writing, thank you for your support.

Same series of articles

  1. A singleton of JavaScript design patterns
  2. JavaScript design pattern strategy pattern
  3. JavaScript design pattern proxy pattern
  4. Iterator pattern for JavaScript design pattern
  5. Publish – subscribe JavaScript design pattern
  6. JavaScript design mode command mode
  7. A combination of JavaScript design patterns
  8. JavaScript design pattern template method pattern
  9. Meta-patterns for JavaScript design patterns
  10. The JavaScript design pattern’s chain of responsibility pattern
  11. The JavaScript design pattern mediator pattern
  12. Decorator pattern for JavaScript design pattern
  13. JavaScript design pattern state pattern
  14. Adapter pattern for JavaScript design pattern