Design Patterns in Go: State

Introduction

The state pattern is a behavourial state pattern, which allows an object to change its behaviour when its internal state changes. Since this sounds quite cryptic, let’s have a look at the diagram:

A short breakdown:

  1. Context has two component in this simplified version: a field called state, which is an interface type, and an operation() method.
  2. When the operation() method is called, the Context object calls the operation() on its state field. Since the state field can hold any implementation of the State interface, the behaviour of Context can change.

As you can see this is not the most complex of Design patterns.

Implementation in Go

In an empty directory open your commandline or terminal and type:

go mod init github.com/state_pattern

Then open your favourite IDE and add a main.go file. First we will add the preliminary stuff:

package main

import "fmt"

Next we will define the State interface:

type State interface {
	Handle() error
}

The State interface only has one method, Handle() which can behave differently in different implementations as we will see.

Now we will define the first state:

type StateA struct{}

func (s *StateA) Handle() error {
	fmt.Println("Handling state A")
	return nil
}

The type StateA is an empty struct for simplicity’s sake. All the Handle() method does here is print out a message and return nil, since we have no errors.

The StateB implementation is similar:

type StateB struct{}

func (s *StateB) Handle() error {
	fmt.Println("Handling state B")
	return nil
}

Now it is time to define the Context:

type Context struct {
	state State
}

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

func (c *Context) Handle() error {
	return c.state.Handle()
}

A short explanation:

  1. The Context struct only has one field, state, which is of an interface type.
  2. Setting this state field is done in the SetState method.
  3. The handle() method of either state is passed down to the state variable and called on that.

Now we can test it:

func main() {
	ctx := &Context{state: &StateA{}}
	ctx.Handle()
}

A line by line breakdown:

  1. We initialize a Context struct and pass it a new instance of the StateA struct.
  2. The Handle() method is called on the context, this method call is passed down to the state object. That way, the behaviour of Context can change on the fly.

Conclusion

As you can see the implementation of the State pattern is very straightforward. It is also a good example of the Inversion of Control Pattern, since the Context struct only talks to interfaces, never to concrete classes.

The power of thinking like this and using patterns like this, must never be underestimated. Using a pattern like this makes it possible to build both extendable and maintainable systems.

Leave a Reply

Your email address will not be published. Required fields are marked *