TypeScript State Pattern Example

1. Definition

The State Design Pattern allows an object to change its behavior when its internal state changes. The object will appear to change its class, but the main advantage is to encapsulate states into separate classes.

2. Problem Statement

Imagine having a system where an object has multiple conditional behaviors depending on its current state. Using conditionals like if-else or switch-case to manage these behaviors can make the code complex, hard to maintain, and error-prone.

3. Solution

The State pattern suggests that you create new classes for all possible states of an object and extract all state-specific behaviors into these classes. The original object, called context, stores a reference to a state object that represents its current state and delegates it the state-related work.

4. Real-World Use Cases

1. Modeling state machines in digital logic or software applications.

2. Implementing multi-mode tools or editors.

3. Workflow engines where entities transition through various states.

5. Implementation Steps

1. Declare an interface for the state.

2. Implement concrete state classes for each specific state.

3. Define the context class that has a state object to delegate state-specific tasks.

6. Implementation in TypeScript

// Step 1: State interface
interface State {
    handle(context: Context): void;
}
// Step 2: Concrete state classes
class ConcreteStateA implements State {
    handle(context: Context): void {
        console.log('Handling state A behavior');
        context.setState(new ConcreteStateB());
    }
}
class ConcreteStateB implements State {
    handle(context: Context): void {
        console.log('Handling state B behavior');
        context.setState(new ConcreteStateA());
    }
}
// Step 3: Context class
class Context {
    private state: State;
    constructor(state: State) {
        this.state = state;
    }
    setState(state: State): void {
        this.state = state;
    }
    request(): void {
        this.state.handle(this);
    }
}
// Client code
const context = new Context(new ConcreteStateA());
context.request();  // Handling state A behavior
context.request();  // Handling state B behavior

Output:

Handling state A behavior
Handling state B behavior

Explanation:

The 'Context' class has a 'request' method that delegates to the 'handle' method of the State interface.

There are two concrete state classes 'ConcreteStateA' and 'ConcreteStateB' that implement this interface. When the 'handle' method of 'ConcreteStateA' is called, it switches the state of the context to 'ConcreteStateB', and vice versa. This allows for a seamless transition between states and encapsulates state-specific logic.

7. When to use?

Use the State pattern when:

1. An object's behavior depends on its state and it must change its behavior at runtime depending on that state.

2. Operations have large, multi-part conditional statements that depend on the object's state. This state is usually represented by one or several enum constants.

3. A class has multiple behaviors that change based on internal conditions, and you want to make these states explicitly.


Comments