State

State Pattern

Introduction

The State pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. This pattern is akin to having a set of different classes for each state of an object, with each class implementing behavior for that specific state.

Purpose

  • To allow an object to change its behavior when its internal state changes.
  • To encapsulate varying behavior for the same routine based on an object’s internal state.

Use Cases

  • When an object’s behavior depends on its state, and it must change its behavior at runtime depending on that state.
  • When operations have large, multipart conditional statements that depend on the object’s state. This state-specific logic can be moved into separate classes.

Advantages/Disadvantages

Advantages:

  • Organizes state-specific code into separate classes.
  • Simplifies complex conditional statements in the context class.
  • Makes adding new states easier as the system evolves.

Disadvantages:

  • Can increase the number of classes, which may complicate maintenance.
  • If not managed well, it can lead to duplication between similar states.

Implementation in JavaScript

javascript
	class State {
	  handle(context) {}
	}
	
	class ConcreteStateA extends State {
	  handle(context) {
	    console.log('Handling state A.');
	    context.setState(new ConcreteStateB());
	  }
	}
	
	class ConcreteStateB extends State {
	  handle(context) {
	    console.log('Handling state B.');
	    context.setState(new ConcreteStateA());
	  }
	}
	
	class Context {
	  constructor() {
	    this.state = new ConcreteStateA();
	  }
	
	  setState(state) {
	    this.state = state;
	  }
	
	  request() {
	    this.state.handle(this);
	  }
	}
	
	// Usage
	const context = new Context();
	
	context.request(); // Handling state A
	context.request(); // Handling state B

Practical Example

Consider a backend application where you’re implementing a ticket tracking system. The ticket can be in different states (like ‘new’, ‘assigned’, ‘closed’) and the behavior changes based on its current state.

javascript
	class TicketState {
	  next(ticket) {}
	  prev(ticket) {}
	  printStatus() {}
	}
	
	class NewState extends TicketState {
	  next(ticket) {
	    ticket.setState(new AssignedState());
	  }
	
	  printStatus() {
	    console.log('Ticket in New state.');
	  }
	}
	
	class AssignedState extends TicketState {
	  next(ticket) {
	    ticket.setState(new ClosedState());
	  }
	
	  prev(ticket) {
	    ticket.setState(new NewState());
	  }
	
	  printStatus() {
	    console.log('Ticket in Assigned state.');
	  }
	}
	
	class ClosedState extends TicketState {
	  prev(ticket) {
	    ticket.setState(new AssignedState());
	  }
	
	  printStatus() {
	    console.log('Ticket in Closed state.');
	  }
	}
	
	class Ticket {
	  constructor() {
	    this.state = new NewState();
	  }
	
	  setState(state) {
	    this.state = state;
	  }
	
	  next() {
	    this.state.next(this);
	  }
	
	  prev() {
	    this.state.prev(this);
	  }
	
	  printStatus() {
	    this.state.printStatus();
	  }
	}
	
	// Usage
	const ticket = new Ticket();
	ticket.printStatus(); // New state
	ticket.next();
	ticket.printStatus(); // Assigned state
	ticket.next();
	ticket.printStatus(); // Closed state
	ticket.prev();
	ticket.printStatus(); // Assigned state

In this example, Ticket changes its behavior based on its state, represented by NewState, AssignedState, and ClosedState. Each state has different implementations for the next, prev, and printStatus methods.