Design Pattern: State Pattern in TypeScript

In State pattern implementation, an object behaves differently when on some state change. With the change of state, the behavior can change completely.

This article demonstrates State pattern implementations in TypeScript. Check the following examples.

Implementation

Follow the steps below for State pattern implementation-

  • Create an abstract class for the state, this will work as a common interface for all the state classes. Store a reference to the context, set the context in the constructor and pass the current state object.
  • Create multiple concrete state classes, and extend the abstract state class. Call the parent constructor in the constructor and set the context.
  • Create context class. In any method implementation, we can call the methods from the state.
  • For using the implementation, create a context object first. Then pass the context while creating state. Then call the methods from context.

Here is a simple example of state pattern implementation-

// State pattern implementation in TypeScript

// state abstract class
abstract class State {
    protected context: Context;

    constructor(context: Context) {
        this.context = context;
        this.context.setState(this);
    }

    abstract actionOne(): void;
    abstract actionTwo(): void;
}

// context class
class Context {
    private state: State | null = null;

    public setState(state: State): void {
        this.state = state;
    }

    public getState(): State | null {
        return this.state;
    }

    public performActionOne() {
        this.state?.actionOne();
    }

    public performActionTwo() {
        this.state?.actionTwo();
    }
}

// First state class
class ConcreteStateOne extends State {
    constructor(context: Context) {
        super(context);
    }

    public actionOne(): void {
        console.log("Calling 'actionOne' of - 'ConcreteStateOne'");
    }

    public actionTwo(): void {
        console.log("Calling 'actionTwo' of - 'ConcreteStateOne'");
    }
}

// Second state class
class ConcreteStateTwo extends State {
    constructor(context: Context) {
        super(context);
    }

    public actionOne() {
        console.log("Calling 'actionOne' of - 'ConcreteStateTwo'");
    }

    public actionTwo() {
        console.log("Calling 'actionTwo' of - 'ConcreteStateTwo'");
    }
}


// Demo
var context = new Context();

new ConcreteStateOne(context);
context.performActionOne();

new ConcreteStateTwo(context);
context.performActionOne();
context.performActionTwo();

Output of this will be as below-

Calling 'actionOne' of - 'ConcreteStateOne'


Calling 'actionOne' of - 'ConcreteStateTwo'
Calling 'actionTwo' of - 'ConcreteStateTwo'

Examples

Here are a few examples of state pattern implementation in TypeScript-

Example #1: Order State Change

Let’s consider another example of the state change of an online product order.

Order State

  • Create file “order-state.ts”.
  • Create abstract class “OrderState”.
  • Declare a property named “context” of type “OrderContext”. This will be used to store a reference to the context.
  • Define the constructor. Accept the “OrderContext” and set that to “context”. Call the “setState” of the context to pass the state to the context.
  • Declare an abstract method named “process”.
// order-state.ts

import OrderContext from "./order-context";

abstract class OrderState {

    protected context: OrderContext;

    constructor(context: OrderContext) {
        this.context = context;

        this.context.setState(this);
    }

    abstract process(): void;
}

export default OrderState;

Order Check State Class

  • Create file “order-check-state.ts”.
  • Define class “OrderCheckState”.
  • Extend abstract class “OrderState” for this class.
  • Define method “process”. Write code for the processing of order in this state. Then change the state by calling the “setState” of the context, and send the order to next status(we are sending the order to In Progress status here).
// order-check-state.ts

import OrderContext from "./order-context";
import OrderState from "./order-state";

class OrderCheckState extends OrderState {
    constructor(context: OrderContext) {
        super(context);
    }

    process(): void {
        // Write code to process the order
        console.log("Checking the order validity and other information");

        this.context.setState(this.context.getOrderInProgressState());
    }
}

export default OrderCheckState;

Order In-Progress State Class

  • Create file “order-in-progress-state.ts”.
  • Define class “OrderInProgressState” and extend “OrderState”.
  • Define method “process” and at the end of the definition send the order to next status(delivery status).
// order-in-progress-state.ts

import OrderContext from "./order-context";
import OrderState from "./order-state";

class OrderInProgressState extends OrderState {
    constructor(context: OrderContext) {
        super(context);
    }

    process(): void {
        // Write code to process the order
        console.log("Processing the order");

        this.context.setState(this.context.getOrderDeliverState());
    }
}

export default OrderInProgressState;

Order Deliver State Class

  • Create file “order-deliver-state.ts”.
  • Define the class “OrderDeliveryState” and extend abstract class “OrderState”.
  • Define the method “process” and at the end of the definition send the order to next status(receive status).
// order-deliver-state.ts

import OrderContext from "./order-context";
import OrderState from "./order-state";

class OrderDeliverState extends OrderState {
    constructor(context: OrderContext) {
        super(context);
    }

    process(): void {
        // Write code to process the order
        console.log("Delivering the order");

        this.context.setState(this.context.getOrderReceiveState());
    }
}

export default OrderDeliverState;

Order Receive State Class

  • Create file “order-receive-state.ts”.
  • Define the class “OrderDeliveryState”.
  • Extend “OrderState” and define method “process” and part of the extension. At the end of the definition send the order to next status(receive status).
// order-receive-state.ts

import OrderContext from "./order-context";
import OrderState from "./order-state";


class OrderReceiveState extends OrderState {
    constructor(context: OrderContext) {
        super(context);
    }

    process(): void {
        // Write code to process the order
        console.log("Order received");

        this.context.setState(null);
    }
}

export default OrderReceiveState;

Order Context Class

  • Create file “order-context.ts”.
  • Define class “OrderContext”.
  • Define property “state” of type “OrderState”. This will be used to store the current order status.
  • Define properties for each of the different order status- “orderCheckStatus”, “orderInProgressState”, “orderDeliveryState”, “orderReceiveState”. Initialize the value of these properties with a new object of the relevant orders status object.
  • In the constructor set the state to the initial order status “orderCheckState”.
  • Define the method “runNextProcess”, call the “process” method of the current state in the implementation.
  • There are few other methods defined for different purposes.
// order-context.ts

import OrderCheckState from "./order-check-state";
import OrderDeliverState from "./order-deliver-state";
import OrderInProgressState from "./order-in-progress-state";
import OrderReceiveState from "./order-receive-state";
import OrderState from "./order-state";

class OrderContext {
    private state: OrderState | null = null;
    private orderCheckState: OrderState;
    private orderInProgressState: OrderState;
    private orderDeliverState: OrderState;
    private orderReceiveState: OrderState;

    constructor() {
        this.orderCheckState = new OrderCheckState(this);
        this.orderInProgressState = new OrderInProgressState(this);
        this.orderDeliverState = new OrderDeliverState(this);
        this.orderReceiveState = new OrderReceiveState(this);

        // Set the placed state as default
        this.state = this.orderCheckState;
    }

    setState(state: OrderState | null): void {
        this.state = state;
    }

    getState(): OrderState | null {
        return this.state;
    }

    getOrderCheckState(): OrderState {
        return this.orderCheckState;
    }

    getOrderInProgressState(): OrderState {
        return this.orderInProgressState;
    }

    getOrderDeliverState(): OrderState {
        return this.orderDeliverState;
    }

    getOrderReceiveState(): OrderState {
        return this.orderReceiveState;
    }

    runNextProcess(): void {
        if (this.state != null) {
            this.state.process();
        } else {
            console.log("Order processing complete");
        }
    }
}

export default OrderContext;

Demo

For using the implementation, create a new object of the “OrderContext”.

Call the “runNextProcess” method each time to move the order to the next state.

When all order reaches the final state, then there will be no further processing.

// demo.ts

import OrderContext from "./order-context";


const order = new OrderContext();

order.runNextProcess();
order.runNextProcess();
order.runNextProcess();
order.runNextProcess();

// Trying to process after all steps are complete
order.runNextProcess();

Output

Following output will be generated-

Processing the order
Delivering the order
Order received


Order processing complete

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check State pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.