Go allows the embedding of one struct inside another, where the embedded struct methods (both value and pointer receiver) can be accessed as if they were methods of the outer struct.
As it turns out, the embedded struct doesn’t have to be a struct — it can declared as an interface, and/or the implementation can be a function instead of a struct. Declaring an embedded interface makes it possible to have a default implementation provided by a constructor function, while unit testing can provide a test implementation.
There is no limit on the number of structs that can be embedded — this is where the template pattern comes in. The basic idea is as follows:
- Define some interfaces for the methods (ideally, one method per interface).
- Define a template interface that includes all of the above interfaces.
- Define a template function that can operate on an instance of the template interface. It will likely need some additional parameters.
- Define one or more structs that implement the single method interfaces.
See this playground code for an example of the above: https://play.golang.org/p/OUkKc7GReWk
One nice thing about this pattern is that it offers both reusability and flexibility:
- Methods that can be handled in a common way can be satisfied by embedding a struct that provides the common implementation.
- Methods whose operation is unique to the struct can be implemented as an ordinary method.
- A constructor function for a struct can set the embedded structs to implementations that are useful under real world conditions.
- A unit test can construct the struct by manually setting the embedded structs to stub implementations, as necessary.
Other nice features I like about this pattern include:
- The ability to use unexported type aliases so the struct can prevent code outside the package from changing implementations, while still satisfying the template interface needed by the algorithm.
- Implement all the “fiddly bits” of the pattern once in the algorithm, such as error handling code.
- Prevent fixing the same bugs over and over in multiple services due to making the same simple mistakes.