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()
andClose()
methods - In the
Rotate()
method we simply print out a message and returnnil
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:
- We define a
RotationServant
interface with aPerformRotation()
method, which gets passed a window to rotate and the number of degrees. - 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.