Design Pattern: State Pattern in Go

State pattern implements completely different behavior of an object based on some state. This pattern is normally used for heavy processing multiple-step operations.

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

Implementation

Follow the steps below for State pattern implementation in Go-

  • Create an interface to ensure a common interface for all the state structs.
  • Create a struct for the base state. Define a method for generating a new state struct. Embed the context and state interface into the state struct.
  • Create state structs for different states. Embed the state base struct into this, to use the common fields and methods. Define methods declared in the interface.
  • Define struct for context. This context struct should have methods for setting/chaing states. Also define methods for calling methods from state, as the state is a reference to the current state.

Here is a simple example of state pattern implementation-

// State pattern implementation in Go

package main

import "fmt"

// State interface and struct
type IState interface {
	ActionOne()
	ActionTwo()
}

type State struct {
	context *Context
	IState
}

func NewState(iState IState, context *Context) (state *State) {
	state = &State{}
	state.IState = iState
	state.context = context

	context.SetState(state)
	return
}

// First Concrete State Struct
type ConcreteStateOne struct {
	*State
}

func NewConcreteStateOne(context *Context) (concreteStateOne *ConcreteStateOne) {
	concreteStateOne = &ConcreteStateOne{}
	concreteStateOne.State = NewState(concreteStateOne, context)
	return
}

func (concreteStateOne *ConcreteStateOne) ActionOne() {
	fmt.Println("Calling 'actionOne' of - 'ConcreteStateOne'")
}

func (concreteStateOne *ConcreteStateOne) ActionTwo() {
	fmt.Println("Calling 'actionTwo' of - 'ConcreteStateOne'")
}

// Second Concrete State Struct
type ConcreteStateTwo struct {
	*State
}

func NewConcreteStateTwo(context *Context) (concreteStateTwo *ConcreteStateTwo) {
	concreteStateTwo = &ConcreteStateTwo{}
	concreteStateTwo.State = NewState(concreteStateTwo, context)
	return
}

func (concreteStateTwo *ConcreteStateTwo) ActionOne() {
	fmt.Println("Calling 'actionOne' of - 'ConcreteStateTwo'")
}

func (concreteStateTwo *ConcreteStateTwo) ActionTwo() {
	fmt.Println("Calling 'actionTwo' of - 'ConcreteStateTwo'")
}

// Context Struct
type Context struct {
	state *State
}

func NewContext() (context *Context) {
	context = &Context{}
	return
}

func (context *Context) GetState() *State {
	return context.state
}

func (context *Context) PerformActionOne() {
	context.state.ActionOne()
}

func (context *Context) PerformActionTwo() {
	context.state.ActionTwo()
}

func (context *Context) SetState(state *State) {
	context.state = state
}

// Demo
func main() {
	context := NewContext()
	NewConcreteStateOne(context)
	context.PerformActionOne()

	NewConcreteStateTwo(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 Golang-

Example #1: Order State Change

In this example, we are processing an order. We have a processing context struct, and we have structs for processing for each step.

Based on the state change, we are changing the context, and this way next steps are being processed until it reaches the delivered state.

Order Struct

  • Create file “order_state.go”.
  • Define interface “IOrderState”. Declare method “Process”, this will be used for processing of each step.
  • Define struct “OrderState”. Embed “OrderContext” and “IOrderState” into this struct.
  • Create method “NewOrderState”, which accepts  “IOrderState” and “OrderContext” param. Initiate a “OrderState” sruct in this method, set the “IOrderState” and “OrderContext”.
// order_state.go

package main

type IOrderState interface {
	Process()
}

type OrderState struct {
	context *OrderContext
	IOrderState
}

func NewOrderState(iOrderState IOrderState, context *OrderContext) (orderState *OrderState) {
	orderState = &OrderState{}
	orderState.IOrderState = iOrderState
	orderState.context = context

	context.SetState(orderState)

	return
}

Order Check Struct

  • Create file “order_check_state.go”.
  • Define struct “OrderCheckState”.
  • Embed “OrderState” into this struct.
  • Define method “NewOrderCheckState” to create a new structs. Also set the OrderState with a new “OrderState” struct initiation.
  • Implement interface “IOrderState”. Define method “Process” as part of the implementation. In the method implementation set the next state to in progress.
// order_check_state.go

package main

import "fmt"

type OrderCheckState struct {
	*OrderState
}

func NewOrderCheckState(context *OrderContext) (orderCheckState *OrderCheckState) {
	orderCheckState = &OrderCheckState{}
	orderCheckState.OrderState = NewOrderState(orderCheckState, context)
	return
}

func (orderCheckState *OrderCheckState) Process() {
	fmt.Println("Checking the order validity and other information")
	orderCheckState.context.SetState(orderCheckState.context.GetOrderInProgressState())
}

Order In-Progress Struct

  • Create file “order_in_progress_state.go”.
  • Define struct “OrderInProgress”.
  • Define method “NewOrderInPRogressState”.
  • Implement “IOrderState” interface, and define “Process” method for the interface implementation.
// order_in_progress_state.go

package main

import "fmt"

type OrderInProgressState struct {
	*OrderState
}

func NewOrderInProgressState(context *OrderContext) (orderInProgressState *OrderInProgressState) {
	orderInProgressState = &OrderInProgressState{}
	orderInProgressState.OrderState = NewOrderState(orderInProgressState, context)
	return
}

func (orderInProgressState *OrderInProgressState) Process() {
	fmt.Println("Processing the order")
	orderInProgressState.context.SetState(orderInProgressState.context.GetOrderDeliverState())
}

Order Deliver Struct

  • Create file “order_deliver_state.go”.
  • Define struct “OrderDeliverState”. Define method “NewOrderDeliverState” for new struct initiation. And define “Process” for implementing “IOrderState” interface.
// order_deliver_state.go

package main

import "fmt"

type OrderDeliverState struct {
	*OrderState
}

func NewOrderDeliverState(context *OrderContext) (orderDeliverState *OrderDeliverState) {
	orderDeliverState = &OrderDeliverState{}
	orderDeliverState.OrderState = NewOrderState(orderDeliverState, context)
	return
}
func (orderDeliverState *OrderDeliverState) Process() {
	fmt.Println("Delivering the order")
	orderDeliverState.context.SetState(orderDeliverState.context.GetOrderReceiveState())
}

Order Receive Struct

  • Create file “order_receive_state.go”.
  • Define struct “OrderReceiveState”. Define method “NewOrderReceiveState” for new struct initiation. And define “Process” for implementing “IOrderState” interface.
// order_receive_state.go

package main

import "fmt"

type OrderReceiveState struct {
	*OrderState
}

func NewOrderReceiveState(context *OrderContext) (orderReceiveState *OrderReceiveState) {
	orderReceiveState = &OrderReceiveState{}
	orderReceiveState.OrderState = NewOrderState(orderReceiveState, context)
	return
}

func (orderReceiveState *OrderReceiveState) Process() {
	fmt.Println("Order received")
	orderReceiveState.context.SetState(nil)
}

Order Context Struct

  • Create file “order_context.go”.
  • Define struct “OrderContext”.
  • Declare field state(of type IOrderState), and one field for each state and define their types as “IOrderState”.
  • Define method “NewOrderContext” to create/initiate a new struct “OrderContext”. Set the fields with relevant structs. Set the “state” field with the initial order state, which is check state.
  • Define methods for the struct for getting and setting the state – “GetState” and “SetState”. Also, define methods for getting states in each step of order processing.
  • Define method “RunNextProcess” for moving the order state to the next step.
// order_context.go

package main

import "fmt"

type OrderContext struct {
	state IOrderState

	orderCheckState      IOrderState
	orderInProgressState IOrderState
	orderDeliverState    IOrderState
	orderReceiveState    IOrderState
}

func NewOrderContext() (orderContext *OrderContext) {
	orderContext = &OrderContext{}
	orderContext.orderCheckState = NewOrderCheckState(orderContext)
	orderContext.orderInProgressState = NewOrderInProgressState(orderContext)
	orderContext.orderDeliverState = NewOrderDeliverState(orderContext)
	orderContext.orderReceiveState = NewOrderReceiveState(orderContext)
	orderContext.state = orderContext.orderCheckState
	return
}

func (orderContext *OrderContext) GetOrderCheckState() IOrderState {
	return orderContext.orderCheckState
}

func (orderContext *OrderContext) GetOrderInProgressState() IOrderState {
	return orderContext.orderInProgressState
}

func (orderContext *OrderContext) GetOrderDeliverState() IOrderState {
	return orderContext.orderDeliverState
}

func (orderContext *OrderContext) GetOrderReceiveState() IOrderState {
	return orderContext.orderReceiveState
}

func (orderContext *OrderContext) GetState() IOrderState {
	return orderContext.state
}

func (orderContext *OrderContext) SetState(state IOrderState) {
	orderContext.state = state
}

func (orderContext *OrderContext) RunNextProcess() {
	if orderContext.state != nil {
		orderContext.state.Process()
	} else {
		fmt.Println("Order processing complete")
	}
}

Demo

Create a new struct of “OrderContext” using “NewOrderContext”.

Then call the method “RunNextProcess” of that object. As the order is in the check state, so checking is performed and then the state is changed to the next state for processing.

Calling “RunNextPorcess” again will perform the processing and change the state to the delivering state.

The process goes on and the state keeps changing until the order reaches the last state.

// main.go

package main

func main() {
	order := NewOrderContext()

	order.RunNextProcess()
	order.RunNextProcess()
	order.RunNextProcess()
	order.RunNextProcess()

	// Trying to process after all steps are complete
	order.RunNextProcess()
}

Output

Output will be as below-

Checking the order validity and other information
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.