Menu
Coddy logo textTech

State Pattern

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 95 of 107.

The State pattern allows an object to change its behavior when its internal state changes, making it appear as if the object changed its class. While Template Method controls algorithm steps, State encapsulates state-specific behavior into separate objects and delegates to the current state.

In Go, we define a state interface and create concrete states that implement different behaviors:

type State interface {
    Handle(d *Document) string
}

type Document struct {
    state State
}

func (d *Document) SetState(s State) {
    d.state = s
}

func (d *Document) Publish() string {
    return d.state.Handle(d)
}

Each state determines what happens and which state comes next:

type DraftState struct{}

func (s DraftState) Handle(d *Document) string {
    d.SetState(ModerationState{})
    return "Draft submitted for moderation"
}

type ModerationState struct{}

func (s ModerationState) Handle(d *Document) string {
    d.SetState(PublishedState{})
    return "Moderation approved, now published"
}

type PublishedState struct{}

func (s PublishedState) Handle(d *Document) string {
    return "Already published"
}

The same method call produces different results based on the current state:

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // Draft submitted for moderation
fmt.Println(doc.Publish())  // Moderation approved, now published
fmt.Println(doc.Publish())  // Already published

State is ideal for objects with distinct modes of operation, such as order processing workflows, UI components, or connection handlers where behavior depends entirely on the current state.

challenge icon

Challenge

Easy

Let's build a ticket support system using the State pattern! You'll create a support ticket that moves through different stages—from being opened, to being worked on, to being resolved—with each state determining what actions are possible and what happens next.

You'll organize your code across three files:

  • state.go: Define your state interface and the concrete states that represent each stage of a ticket's lifecycle.

    Create a TicketState interface with a method Handle(t *Ticket) string that processes the ticket and potentially transitions it to the next state.

    Implement three states:

    • OpenState — when handled, transitions the ticket to InProgressState and returns Ticket opened, assigning to support team
    • InProgressState — when handled, transitions to ResolvedState and returns Working on ticket, issue resolved
    • ResolvedState — when handled, stays in the same state and returns Ticket already resolved
  • ticket.go: Create your ticket struct that holds the current state and delegates behavior to it.

    Build a Ticket struct with an ID field (string) and a state field (TicketState). Add these methods:

    • SetState(s TicketState) to change the ticket's current state
    • Process() string that delegates to the current state's Handle method

    Create a NewTicket(id string) *Ticket constructor that returns a ticket starting in the OpenState.

  • main.go: Demonstrate how the same action produces different results based on the ticket's current state.

    Read a ticket ID and the number of times to process the ticket. Create a new ticket with that ID, then call Process() the specified number of times, printing each result on a separate line.

The following inputs will be provided:

  • Line 1: Ticket ID
  • Line 2: Number of times to process the ticket

For example, given:

TKT-001
3

Your output should be:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved

And given:

TKT-500
5

Your output should be:

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved

And given:

ISSUE-42
1

Your output should be:

Ticket opened, assigning to support team

Notice how calling Process() on the same ticket produces different results each time—the ticket's behavior changes as it moves through states. Once resolved, it stays resolved no matter how many times you process it. The ticket object appears to change its behavior, but it's actually delegating to different state objects!

Cheat sheet

The State pattern allows an object to change its behavior when its internal state changes. The object delegates behavior to separate state objects representing different states.

Define a state interface:

type State interface {
    Handle(d *Document) string
}

Create a context object that holds the current state:

type Document struct {
    state State
}

func (d *Document) SetState(s State) {
    d.state = s
}

func (d *Document) Publish() string {
    return d.state.Handle(d)
}

Implement concrete states that encapsulate state-specific behavior:

type DraftState struct{}

func (s DraftState) Handle(d *Document) string {
    d.SetState(ModerationState{})
    return "Draft submitted for moderation"
}

type ModerationState struct{}

func (s ModerationState) Handle(d *Document) string {
    d.SetState(PublishedState{})
    return "Moderation approved, now published"
}

type PublishedState struct{}

func (s PublishedState) Handle(d *Document) string {
    return "Already published"
}

Each state determines what happens and transitions to the next state:

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // Draft submitted for moderation
fmt.Println(doc.Publish())  // Moderation approved, now published
fmt.Println(doc.Publish())  // Already published

The State pattern is ideal for objects with distinct modes of operation where behavior depends entirely on the current state.

Try it yourself

package main

import (
	"fmt"
)

func main() {
	// Read input
	var ticketID string
	var numProcesses int
	fmt.Scanln(&ticketID)
	fmt.Scanln(&numProcesses)

	// TODO: Create a new ticket with the given ID

	// TODO: Process the ticket the specified number of times
	// and print each result on a separate line
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming