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:
- Go embedding is in essence composition, the structs become part of the new object. The embedded struct becomes a field
- 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:
- A Rectangle only has a width and a height in our example.
- The newRectangle() function is a utility function to make a new rectangle.
- 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:
- As mentioned our TextWindow has two properties, a title and an embedded Rectangle. struct.
- The openWindow() function is a utility function to create a new window.
- 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:
- We create a TextWindow struct
- A call is made to the area() method
- 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.