Browse TypeScript Design Patterns & Application Architecture

Implementing State Pattern in TypeScript: Encapsulating State-Specific Behavior

Learn how to implement the State Pattern in TypeScript to encapsulate state-specific behavior in classes and manage state transitions effectively.

6.8.1 Implementing State in TypeScript

The State Pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. This pattern is particularly useful when an object must change its behavior at runtime depending on its state. In TypeScript, we can implement the State Pattern by encapsulating state-specific behavior in classes and managing state transitions effectively.

Understanding the State Pattern

The State Pattern is about encapsulating the state-specific behavior of an object in separate classes, known as state classes. The object, known as the context, maintains a reference to a state object and delegates its behavior to the current state object. This pattern is beneficial in scenarios where an object can be in multiple states, and its behavior changes depending on its current state.

Key Components of the State Pattern

  1. State Interface: Defines the interface for encapsulating the behavior associated with a particular state of the context.
  2. Concrete State Classes: Implement the state interface and define specific behavior for each state.
  3. Context Class: Maintains a reference to a state instance and delegates state-specific behavior to the current state object. It also allows changing the current state.

Implementing the State Pattern in TypeScript

Let’s explore how to implement the State Pattern in TypeScript with a practical example.

Step 1: Define the State Interface

First, we define a State interface that declares methods representing possible actions.

1// State.ts
2export interface State {
3    handleRequest(): void;
4}

The handleRequest method is a placeholder for state-specific behavior.

Step 2: Implement Concrete State Classes

Next, we implement concrete state classes that define specific behaviors for each state.

 1// ConcreteStateA.ts
 2import { State } from './State';
 3
 4export class ConcreteStateA implements State {
 5    handleRequest(): void {
 6        console.log("ConcreteStateA handles the request.");
 7    }
 8}
 9
10// ConcreteStateB.ts
11import { State } from './State';
12
13export class ConcreteStateB implements State {
14    handleRequest(): void {
15        console.log("ConcreteStateB handles the request.");
16    }
17}

Each concrete state class implements the State interface and provides its own implementation of the handleRequest method.

Step 3: Create the Context Class

The Context class maintains a reference to a State instance and delegates actions to it. It also provides methods to change its current state.

 1// Context.ts
 2import { State } from './State';
 3
 4export class Context {
 5    private state: State;
 6
 7    constructor(initialState: State) {
 8        this.state = initialState;
 9    }
10
11    setState(state: State): void {
12        console.log(`Context: Transition to ${state.constructor.name}`);
13        this.state = state;
14    }
15
16    request(): void {
17        this.state.handleRequest();
18    }
19}

The Context class has a setState method that allows changing the current state and a request method that delegates the action to the current state.

Step 4: Demonstrate State Transitions

Let’s demonstrate how state transitions are handled within the Context or State classes.

 1// Main.ts
 2import { Context } from './Context';
 3import { ConcreteStateA } from './ConcreteStateA';
 4import { ConcreteStateB } from './ConcreteStateB';
 5
 6// Initialize context with ConcreteStateA
 7const context = new Context(new ConcreteStateA());
 8
 9// Handle request in ConcreteStateA
10context.request();
11
12// Transition to ConcreteStateB
13context.setState(new ConcreteStateB());
14
15// Handle request in ConcreteStateB
16context.request();

In this example, the Context is initialized with ConcreteStateA. When the request method is called, it delegates the action to ConcreteStateA. The Context then transitions to ConcreteStateB, and subsequent calls to request are handled by ConcreteStateB.

Benefits of the State Pattern

The State Pattern offers several benefits in terms of code organization and avoiding complex conditionals:

  1. Encapsulation of State-Specific Behavior: By encapsulating state-specific behavior in separate classes, we achieve a cleaner and more organized codebase.
  2. Ease of Adding New States: Adding new states becomes straightforward as we only need to create new state classes without modifying existing code.
  3. Avoidance of Complex Conditionals: The State Pattern eliminates the need for complex conditional statements to handle different states, resulting in more maintainable code.

Visualizing the State Pattern

To better understand the State Pattern, let’s visualize the relationships between the Context, State, and ConcreteState classes using a class diagram.

    classDiagram
	    class Context {
	        - State state
	        + setState(State): void
	        + request(): void
	    }
	
	    class State {
	        <<interface>>
	        + handleRequest(): void
	    }
	
	    class ConcreteStateA {
	        + handleRequest(): void
	    }
	
	    class ConcreteStateB {
	        + handleRequest(): void
	    }
	
	    Context --> State
	    ConcreteStateA ..|> State
	    ConcreteStateB ..|> State

Diagram Description: This class diagram illustrates the relationships between the Context, State, and ConcreteState classes. The Context class maintains a reference to a State instance and delegates actions to it. The ConcreteStateA and ConcreteStateB classes implement the State interface and define specific behaviors for each state.

Try It Yourself

To deepen your understanding of the State Pattern, try modifying the code examples:

  • Add a New State: Implement a new concrete state class, ConcreteStateC, and demonstrate its behavior within the Context.
  • Extend State Behavior: Add additional methods to the State interface and implement them in the concrete state classes.
  • Experiment with State Transitions: Modify the Context class to automatically transition between states based on certain conditions.

References and Further Reading

Knowledge Check

Before we conclude, let’s reinforce our understanding with a few questions:

  • What are the key components of the State Pattern?
  • How does the State Pattern help in avoiding complex conditionals?
  • What are the benefits of encapsulating state-specific behavior in separate classes?

Embrace the Journey

Remember, mastering design patterns like the State Pattern is a journey. As you continue to explore and implement these patterns, you’ll gain deeper insights into writing maintainable and scalable code. Keep experimenting, stay curious, and enjoy the process!

Quiz Time!

Loading quiz…
Revised on Thursday, April 23, 2026