State Pattern

Allow an object to alter its behavior when its internal state changes

Understanding the State Pattern

The State pattern is a behavioral design pattern that allows an object to alter its behavior when its internal state changes. The object will appear to change its class as its behavior changes based on the state it's in.

Problem

Imagine you're developing a document editing application with a document that can be in different states: Draft, Moderation, Published. Each state determines what operations can be performed on the document. For example:

Implementing this with conditional statements (like if/else or switch) can lead to several problems:

Solution

The State pattern suggests creating new classes for all possible states of an object and extracting state-specific behaviors into these classes. The original object (called Context) stores a reference to one of the state objects that represents its current state and delegates all state-specific work to that object.

When the context's state changes, it simply switches to another state object. This way:

Structure

State Pattern Structure

Participants

When to Use

Use the State Pattern when:

Benefits

Real-World Uses

Implementation Example

Here's a JavaScript implementation of the State pattern for a document workflow system:

// Context class
class Document {
    constructor() {
        // Initialize with Draft state
        this.state = new DraftState(this);
        this.content = "";
    }
    
    // Methods that delegate to the current state
    edit(content) {
        this.state.edit(content);
    }
    
    review() {
        this.state.review();
    }
    
    approve() {
        this.state.approve();
    }
    
    reject() {
        this.state.reject();
    }
    
    publish() {
        this.state.publish();
    }
    
    // Method to change the document's state
    changeState(state) {
        this.state = state;
    }
    
    // Business logic methods that can be called by states
    setContent(content) {
        this.content = content;
    }
    
    getContent() {
        return this.content;
    }
}

// State interface (abstract class in JS)
class DocumentState {
    constructor(document) {
        this.document = document;
    }
    
    edit(content) {
        console.log("Operation not allowed in current state");
    }
    
    review() {
        console.log("Operation not allowed in current state");
    }
    
    approve() {
        console.log("Operation not allowed in current state");
    }
    
    reject() {
        console.log("Operation not allowed in current state");
    }
    
    publish() {
        console.log("Operation not allowed in current state");
    }
}

// Concrete State: Draft
class DraftState extends DocumentState {
    constructor(document) {
        super(document);
    }
    
    edit(content) {
        this.document.setContent(content);
        console.log("Document updated with new content");
    }
    
    review() {
        console.log("Document submitted for review");
        this.document.changeState(new ModerationState(this.document));
    }
}

// Concrete State: Moderation
class ModerationState extends DocumentState {
    constructor(document) {
        super(document);
    }
    
    approve() {
        console.log("Document has been approved");
        this.document.changeState(new PublishedState(this.document));
    }
    
    reject() {
        console.log("Document has been rejected, returning to draft");
        this.document.changeState(new DraftState(this.document));
    }
}

// Concrete State: Published
class PublishedState extends DocumentState {
    constructor(document) {
        super(document);
    }
    
    edit(content) {
        console.log("Creating a new draft from published document");
        // First, transition back to draft
        this.document.changeState(new DraftState(this.document));
        // Then edit the content
        this.document.edit(content);
    }
}

// Client code
function clientCode() {
    const document = new Document();
    
    // Initial draft
    document.edit("Initial draft content");
    
    // Submit for review
    document.review();
    
    // Try to edit while in moderation (not allowed)
    document.edit("Trying to edit in moderation");
    
    // Approve the document
    document.approve();
    
    // Try to approve again (not allowed in published state)
    document.approve();
    
    // Edit the published document (creates a new draft)
    document.edit("Updated content for new draft");
    
    // Submit for review again
    document.review();
    
    // Reject this time
    document.reject();
    
    // Now we're back in draft, can edit again
    document.edit("Improved content after rejection");
}

clientCode();

In this example:

Interactive Demo

Experience the State pattern in action with this interactive demo of a document workflow system. See how the document's behavior changes as it transitions between different states.

Interactive Demo: Document Workflow

Use the controls below to transition the document through various states and see how its behavior changes.

Draft
Moderation
Published
Current State:
Draft
Current Content:
No content yet
Document initialized in Draft state