Easy delegation in Go: the delegation pattern

Introduction

In delegation, you delegate a certain request to an object to a second object, which we call the delegate. This is almost like a subclass sending a request to its parent class. In Go we can achieve this functionality with embedding. I must however stress that embedding is not true subclassing as we shall see.

Why is Go embedding not subclassing?

There are two main reasons:

  1. Go embedding is in essence composition, the structs become part of the new object. The embedded struct becomes a field
  2. When a struct is embedded, the embedding struct does not become interchangeable with the embedded struct. In the case of classical inheritance this would be the case.

Implementation in Go

In this example we will implement a virtual UI with a TextWindow which has a width and a height, both are integer. For our UI it is important to know that area of the window.

We will start with the usual preliminaries:

package main

import "fmt"

Next we will implement the Bounds interface, which has one method:

type Bounds interface {
	area() int
}

In our UI a TextWindow consists of a title, represented as a string and a Rectangle.

We will start with the Rectangle struct:

type Rectangle struct {
	width  int
	height int
}

func newRectange(width int, height int) *Rectangle {
	return &Rectangle{
		width:  width,
		height: height,
	}
}

func (r *Rectangle) area() int {
	return r.width * r.height
}

Some explanation is needed:

  1. A Rectangle only has a width and a height in our example.
  2. The newRectangle() function is a utility function to make a new rectangle.
  3. And we finally implement the Bounds interface for the Rectangle

Next we come to the TextWindow struct:

type TextWindow struct {
	title string
	Rectangle
}

func openWindow(title string, width int, height int) *TextWindow {
	return &TextWindow{
		title: title,
		Rectangle: Rectangle{
			width:  width,
			height: height,
		},
	}
}

func (w *TextWindow) area() int {
	return w.Rectangle.area()
}

Some remarks on this code:

  1. As mentioned our TextWindow has two properties, a title and an embedded Rectangle. struct.
  2. The openWindow() function is a utility function to create a new window.
  3. We also implement the Bounds interface to calculate the area. It is in here the call to area() is delegated to the Rectangle struct.

One thing I found out is that in this case you do not need to implement the Bounds interface for the TextWindow at all. There is only one field in the struct implementing that interface, Rectangle, and Go automatically delegates the area() call to that.

Basically, TextWindow implements the Bounds interface because Rectangle implements that. Mind you, had you had two structs implementing that interface, for example a Square struct, you have had to explicitly implement the interface Bounds on TextWindow. That has to do with the fact that in such a case Go does not know to which struct to delegate the call.

Time to test

The test code looks quite simple:

func main() {
	window := openWindow("My windows", 80, 40)
	area := window.area()
	fmt.Printf("Area of the window is %d", area)
}

Line by line:

  1. We create a TextWindow struct
  2. A call is made to the area() method
  3. And we print the resulting area.

To show you that TextWindow actually implements the Bounds interface, change the first line to:

var window Bounds = openWindow("My windows", 80, 40)

And it will still work.

Conclusion

Implementing this pattern was pretty easy, both to build and understand. However, this pattern also teaches some points about Go’s typesystem which seems simple at first but turns out to be quite refined.

Leave a Reply

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