Easy Mastery: Exploring the Servant Pattern in Go for Effortless Design

Photo by Ann H: https://www.pexels.com/photo/wooden-figurines-in-rows-7422276/

Introduction

The Servant pattern is a way of organizing code where one special object helps out a bunch of other objects. This helper object provides some functions to these objects, so they don’t have to do the same things over and over again. This pattern makes it easier to add new features and test the code.

Difference between the Servant pattern and Dependency Injection

The Servant pattern helps us keep things separate and make our systems more flexible. It adds extra features to existing objects. On the other hand, Dependency Injection reduces connections between different parts of the code. It gives a class the things it needs to work, usually in a specific form, instead of making the class create them.

Implementation in Go

We’ll make a simple window system using this pattern. There are two types of windows: ones that can rotate and ones that can’t. We create these windows and a helper, the Servant, to make them rotate.

We will start with the usual:

package main

import "fmt"

Next we define the Rotatable interface, which both kinds of windows implement in a different way:

type Rotatable interface {
	Rotate(degrees int) error
}

Now it is time to define the first kind of window, which we can rotate:

type RotatableWindow struct {
	Title string
}

func createRotatableWindow(title string) RotatableWindow {
	return RotatableWindow{
		Title: title,
	}
}

func (rw RotatableWindow) Open() {
	fmt.Printf("Opening window with title: %s\n", rw.Title)
}

func (rw RotatableWindow) Close() {
	fmt.Printf("Closing window with title: %s\n", rw.Title)
}

func (rw RotatableWindow) Rotate(degrees int) error {
	fmt.Printf("Rotating by %d degrees\n", degrees)
	return nil
}

Some noteworthy points:

  • We define a utility-function to create a window
  • There are the normal Open() and Close() methods
  • In the Rotate() method we simply print out a message and return nil since no error occurred.

Now for contrast we also implement a non-rotatable window:

type NonRotatableWindow struct {
	Title string
}

func createNonRotatableWindow(title string) NonRotatableWindow {
	return NonRotatableWindow{
		Title: title,
	}
}

func (nr NonRotatableWindow) Open() {
	fmt.Printf("Opening window with title: %s\n", nr.Title)
}

func (nr NonRotatableWindow) Close() {
	fmt.Printf("Closing window with title: %s\n", nr.Title)
}

func (nr NonRotatableWindow) Rotate(degrees int) error {
	return fmt.Errorf("You can not rotate this window")
}

This is almost the same as the rotatable window, however, now the Rotate() method returns an error.

Now we come to the Servant, after which this pattern is named:

type RotationServant interface {
	PerformRotation(window Rotatable, degrees int) error
}

type WindowsRotatorServant struct{}

func (wr WindowsRotatorServant) PerformRotation(window Rotatable, degrees int) error {
	return window.Rotate(degrees)
}

Some notes:

  1. We define a RotationServant interface with a PerformRotation() method, which gets passed a window to rotate and the number of degrees.
  2. The PerformRotation() method returns an error, in case the window can not be rotated.

Time to test

Let’s test this setup:

func main() {
	rotator := WindowsRotatorServant{}

	windowA := createRotatableWindow("Rotatable window")
	windowA.Open()
	err := rotator.PerformRotation(windowA, 45)
	if err != nil {
		fmt.Println(err)
	}
	windowA.Close()

	windowB := createNonRotatableWindow("Non-rotatable window")
	windowB.Open()
	err = rotator.PerformRotation(windowB, 45)
	if err != nil {
		fmt.Println(err)
	}
	windowB.Close()
}

We make a rotatable window and a non-rotatable window, then try to rotate them. The Servant helps the rotatable one rotate, but it can’t help the non-rotatable one.

Conclusion

Even though it was a bit tricky to find a good example, the Servant pattern is a neat way to add features to your classes without changing their code. It’s especially useful when you want to keep different tasks separate, like rotating windows in our example. It makes the code easy to put together and maintain.

Leave a Reply

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