Register to get access to free programming courses with interactive exercises

Pattern State JS: Polymorphism

The State pattern is a prime example of replacing conditional constructs with subtype polymorphism. It's quite widely used and can really reduce the complexity of your code. Let's look at the behavior of phone screens as an example.

Not all phones behave the same way, but we needed to choose a specific example for the lesson

A phone has a total of three basic states:

  1. The phone is switched off. The screen does not respond to being touched.
  2. The phone is on, but the screen is off. The screen only responds to being touched (not swiped) and turns on.
  3. The phone is on and so is the screen. The response to touches and gestures depends on the active application.

Let's model this logic in the class responsible for the screen and add two events to it: touch and swipe.

class MobileScreen {
  constructor() {
    // At the beginning, the phone is turned off
    this.powerOn = false;
    this.screenOn = false;
  }

  // Turning the power on
  powerOn() {
    this.powerOn = true;
  }

  // Touch
  touch() {
    // If the power is off, nothing happens
    if (!this.powerOn) {
      return;
    }

    // If the screen is turned off, it must be turned on
    if (!this.screenOn) {
      this.screenOn = true;
    }

    // The currently active application should be the one responding to the event
    this.notify('touch');
  }

  // Swiping
  swipe() {
    // If the power or screen is off, nothing happens
    if (!this.powerOn || !this.screenOn) {
      return;
    }

    // The currently active application should be the one responding to the event
    this.notify('swipe');
  }
}

There are only two events, and the same number of conditional constructs. In reality, there would be many more events, and they all have to take into account the state of the phone and screen activity.

If we solve this problem head-on, we get a huge number of conditional constructs in the method for each event. Such code is very complex and fragile. Changing the number of states and adding new events means risking lots and lots of bugs. It's hard to see the whole picture and not miss something.

The complexity of such code can be greatly reduced by two successive transformations: the allocation of the explicit state and the connection of the polymorphism of subtypes.

Explicitly highlighted state

The current implementation of the screen relies on flags. In programming, this is the name given to variables that contain boolean values.

constructor() {
  this.powerOn = false;
  this.screenOn = false;
}

Flags are often (but not always!) a sign of bad architecture. They tend to multiply and overlap. Logic tied to combinations of different flags complicates code analysis:

if (!this.powerOn || !this.screenOn) {
  return;
}

This style of programming has its own name: "flag programming". That's what they say about code that's hard to figure out because it has its logic tied to a combination of flags. And having flags will almost certainly lead to that. The issue is that systems usually have more than two states. I.e., one flag will never be enough.

It's possible to get away from flags by introducing an explicit system state. In our example, it's easy to see that there are only three states:

  • Power Off: The power is off (and therefore the screen is off).
  • Screen Disabled: The screen is off (but the power is on).
  • Screen On: The screen is on.

The next step is to replace the flags with a single variable that stores the current state of the system:

class MobileScreen {
  constructor() {
    this.stateName = 'powerOff';
  }

  powerOn() {
    this.stateName = 'screenDisabled';
  }

  touch() {
    if (this.stateName === 'powerOff') {
      return;
    }

    if (this.stateName === 'screenDisabled') {
      this.stateName = 'screenOn';
    }

    this.notify('touch');
  }

  swipe() {
    if (this.stateName !== 'screenOn') {
      return;
    }

    // The currently active application should be the one responding to the event
    this.notify('swipe');
  }
}

The main thing about the code above was that there were no checks for the combination of flags. This doesn't mean we won't have to check several states at once, but system states are much easier to understand than sets of flags.

Classes of States

You'll need polymorphism to get rid of conditional constructions. What should it be built on? Due to the presence of an explicitly allocated state, it's easy to see the dependence of behavior on the state. It's the states that must transform into classes with their own state-specific behavior.

The screen, in turn, will get rid of all checks and start interacting with states:

import PowerOffState from './states/PowerOffState.js';
import ScreenDisabledState from './states/ScreenDisabledState.js';
import ScreenOnState from './states/ScreenOnState.js';

class MobileScreen {
  constructor() {
    // A list of states is needed to switch between them
    // Otherwise cyclic dependencies may appear within states
    this.states = {
      PowerOff: PowerOffState,
      ScreenDisabled: ScreenDisabledState,
      ScreenOn: ScreenOnState,
    }
    // Initial state
    // The current object is passed to it
    // This is needed to change states (examples below)
    this.state = new this.states.PowerOff(this);
  }

  powerOn() {
    // We don't care about the previous state
    // All data is stored in the screen itself
    // State objects have no data of their own
    this.state = new this.states.ScreenDisabled(this);
  }

  touch() {
    this.state.touch();
  }

  swipe() {
    this.state.swipe();
  }
}

// Note that from the external code's perspective,
// nothing has changed.

Now the screen does absolutely nothing. All of its code is initializing the initial state and transferring control to the current active state. So what do the state classes look like?

class PowerOffState {
  constructor(screen) {
    this.screen = screen;
  }

  touch() {
    // nothing is happening
  }

  swipe() {
    // nothing is happening
  }
}

The simplest of all is the phone's off state In this state, there's no reaction, so the methods are empty. Let's look at ScreenDisabledState:

class ScreenDisabledState {
  constructor(screen) {
    this.screen = screen;
  }

  touch() {
    // Turning on the screen. You need to pass the screen itself to the constructor.
    this.screen.state = new this.screen.states.ScreenOn(this.screen);
    // Alert the current program that the phone's been activated
    this.screen.notify('touch');
  }

  swipe() {
    // nothing is happening
  }
}

Touching the screen wakes it up. To do this,ScreenDisabledState must switch to ScreenOnState. That's why the screen itself was passed to each state. Otherwise it would be impossible to change it.

And the last state is ScreenOnState. This is the only state in which there is interaction with programs

class ScreenOnState {
  constructor(screen) {
    this.screen = screen;
  }

  touch() {
    this.screen.notify('touch');
  }

  swipe() {
    this.screen.notify('swipe');
  }
}

Incredibly, there are no more conditional constructs in the code. It's now easy to see the behavior of the phone for all events in a particular state. You just need to open the right class. This convenience comes at the cost of needing more files and codes.

It's very important not to miss the main idea of the pattern. State classes are only introduced to introduce polymorphism; they have no data of their own to work with. Ultimately, all the impact goes to the screen itself, the entity that we're simplifying.


Hexlet Experts

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
130
courses
1000
exercises
2000+
hours of theory
3200
tests

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.