We are going to start our journey through the world of structural patterns. Structural patterns, as the name implies, help us to shape our applications with commonly used structures and relationships.
The Go language, by nature, encourages use of composition almost exclusively by its lack of inheritance. Because of this, we have been using the Composite design pattern extensively until now, so let's start by defining the Composite design pattern.
The Composite design pattern favors composition (commonly defined as a has a relationship) over inheritance (an is a relationship). The composition over inheritance approach has been a source of discussions among engineers since the nineties. We will learn how to create object structures by using a has a approach. All in all, Go doesn't have inheritance because it doesn't need it!
In the Composite design pattern, you will create hierarchies and trees of objects. Objects have different objects with their own fields and methods inside them. This approach is very powerful and solves many problems of inheritance and multiple inheritances. For example, a typical inheritance problem is when you have an entity that inherits from two completely different classes, which have absolutely no relationship between them. Imagine an athlete who trains, and who is a swimmer who swims:
Athlete
class has a Train()
methodSwimmer
class has a Swim()
methodThe Swimmer
class inherits from the Athlete
class, so it inherits its Train
method and declares its own Swim
method. You could also have a cyclist who is also an athlete, and declares a Ride
method.
But now imagine an animal that eats, like a dog that also barks:
Cyclist
class has a Ride()
methodAnimal
class has Eat()
, Dog()
, and Bark()
methodsNothing fancy. You could also have a fish that is an animal, and yes, swims! So, how do you solve it? A fish cannot be a swimmer that also trains. Fish don't train (as far as I know!). You could make a Swimmer
interface with a Swim
method, and make the swimmer athlete and fish implement it. This would be the best approach, but you still would have to implement swim
method twice, so code reusability would be affected. What about a triathlete? They are athletes who swim, run, and ride. With multiple inheritances, you could have a sort of solution, but that will become complex and not maintainable very soon.
As you have probably imagined already, the objective of the composition is to avoid this type of hierarchy hell where the complexity of an application could grow too much, and the clarity of the code is affected.
We will solve the described problem of the athlete and the fish that swims in a very idiomatic Go way. With Go, we can use two types of composition--the direct composition and the embedding composition. We will first solve this problem by using direct composition which is having everything that is needed as fields within the struct.
Requirements are like the ones described previously. We'll have an athlete and a swimmer. We will also have an animal and a fish. The Swimmer
and the Fish
methods must share the code. The athlete must train, and the animal must eat:
Athlete
struct with a Train
methodSwimmer
with a Swim
methodAnimal
struct with an Eat
methodFish
struct with a Swim
method that is shared with the Swimmer
, and not have inheritance or hierarchy issuesThe Composite design pattern is a pure structural pattern, and it doesn't have much to test apart from the structure itself. We won't write unit tests in this case, and we'll simply describe the ways to create those compositions in Go.
First, we'll start with the Athlete
structure and its Train
method:
type Athlete struct{} func (a *Athlete) Train() { fmt.Println("Training") }
The preceding code is pretty straightforward. Its Train
method prints the word Training
and a new line. We'll create a composite swimmer that has an Athlete
struct inside it:
type CompositeSwimmerA struct{ MyAthlete Athlete MySwim func() }
The CompositeSwimmerA
type has a MyAthlete
field of type Athlete
. It also stores a func()
type. Remember that in Go, functions are first-class citizens and they can be used as parameters, fields, or arguments just like any variable. So CompositeSwimmerA
has a MySwim
field that stores a closure, which takes no arguments and returns nothing. How can I assign a function to it? Well, let's create a function that matches the func()
signature (no arguments, no return):
func Swim(){ fmt.Println("Swimming!") }
That's all! The Swim()
function takes no arguments and returns nothing, so it can be used as the MySwim
field in the CompositeSwimmerA
struct:
swimmer := CompositeSwimmerA{ MySwim: Swim, } swimmer.MyAthlete.Train() swimmer.MySwim()
Because we have a function called Swim()
, we can assign it to the MySwim
field. Note that the Swim
type doesn't have the parenthesis that will execute its contents. This way we take the entire function and copy it to MySwim
method.
But wait. We haven't passed any athlete to the MyAthlete
field and we are using it! It's going to fail! Let's see what happens when we execute this snippet:
$ go run main.go Training Swimming!
That's weird, isn't it? Not really because of the nature of zero-initialization in Go. If you don't pass an Athlete
struct to the CompositeSwimmerA
type, the compiler will create one with its values zero-initialized, that is, an Athlete
struct with its fields initialized to zero. Check out
Chapter 1,
Ready... Steady... Go! to recall zero-initialization if this seems confusing. Consider the CompositeSwimmerA
struct code again:
type CompositeSwimmerA struct{ MyAthlete Athlete MySwim func() }
Now we have a pointer to a function stored in the MySwim
field. We can assign the Swim
function the same way, but with an extra step:
localSwim := Swim swimmer := CompositeSwimmerA{ MySwim: localSwim, } swimmer.MyAthlete.Train() swimmer.MySwim ()
First, we need a variable that contains the function Swim
. This is because a function doesn't have an address to pass it to the CompositeSwimmerA
type. Then, to use this function within the struct, we have to make a two-step call.
What about our fish problem? With our Swim
function, it is not a problem anymore. First, we create the Animal
struct:
type Animal struct{} func (r *Animal)Eat() { println("Eating") }
Then we'll create a Shark
object that embeds the Animal
object:
type Shark struct{ Animal Swim func() }
Wait a second! Where is the field name of the Animal
type? Did you realize that I used the word embed in the previous paragraph? This is because, in Go, you can also embed objects within objects to make it look a lot like inheritance. That is, we won't have to explicitly call the field name to have access to its fields and method because they'll be part of us. So the following code will be perfectly okay:
fish := Shark{ Swim: Swim, } fish.Eat() fish.Swim()
Now we have an Animal
type, which is zero-initialized and embedded. This is why I can call the Eat
method of the Animal
struct without creating it or using the intermediate field name. The output of this snippet is the following:
$ go run main.go
Eating
Swimming!
Finally, there is a third method to use the Composite pattern. We could create a Swimmer
interface with a Swim
method and a SwimmerImpl
type to embed it in the athlete swimmer:
type Swimmer interface { Swim() } type Trainer interface { Train() } type SwimmerImpl struct{} func (s *SwimmerImpl) Swim(){ println("Swimming!") } type CompositeSwimmerB struct{ Trainer Swimmer }
With this method, you have more explicit control over object creation. The Swimmer
field is embedded, but won't be zero-initialized as it is a pointer to an interface. The correct use of this approach will be the following:
swimmer := CompositeSwimmerB{ &Athlete{}, &SwimmerImpl{}, } swimmer.Train() swimmer.Swim()
And the output for CompositeSwimmerB
is the following, as expected:
$ go run main.go Training Swimming!
Which approach is better? Well, I have a personal preference, which shouldn't be considered the rule of thumb. In my opinion, the interfaces approach is the best for quite a few reasons, but mainly for explicitness. First of all, you are working with interfaces which are preferred instead of structs. Second, you aren't leaving parts of your code to the zero-initialization feature of the compiler. It's a really powerful feature, but one that must be used with care, because it can lead to runtime problems which you'll find at compile time when working with interfaces. In different situations, zero-initialization will save you at runtime, in fact! But I prefer to work with interfaces as much as possible, so this is not actually one of the options.
Another very common approach to the Composite pattern is when working with Binary Tree structures. In a Binary Tree, you need to store instances of itself in a field:
type Tree struct { LeafValue int Right *Tree Left *Tree }
This is some kind of recursive compositing, and, because of the nature of recursivity, we must use pointers so that the compiler knows how much memory it must reserve for this struct. Our Tree
struct stored a LeafValue
object for each instance and a new Tree
in its Right
and Left
fields.
With this structure, we could create an object like this:
root := Tree{ LeafValue: 0, Right:&Tree{ LeafValue: 5, Right: &1Tree{ 6, nil, nil }, Left: nil, }, Left:&Tree{ 4, nil, nil }, }
We can print the contents of its deepest branch like this:
fmt.Println(root.Right.Right.LeafValue)
$ go run main.go
6
When using the Composite design pattern in Go, you must be very careful not to confuse it with inheritance. For example, when you embed a Parent
struct within a Son
struct, like in the following example:
type Parent struct { SomeField int } type Son struct { Parent }
You cannot consider that the Son
struct is also the Parent
struct. What this means is that you cannot pass an instance of the Son
struct to a function that is expecting a Parent
struct like the following:
func GetParentField(p *Parent) int{ fmt.Println(p.SomeField) }
When you try to pass a Son
instance to the GetParentField
method, you will get the following error message:
cannot use son (type Son) as type Parent in argument to GetParentField
This, in fact, makes a lot of sense. What's the solution for this? Well, you can simply composite the Son
struct with the parent without embedding so that you can access the Parent
instance later:
type Son struct { P Parent }
So now you could use the P
field to pass it to the GetParentField
method:
son := Son{} GetParentField(son.P)
At this point, you should be really comfortable using the Composite design pattern. It's a very idiomatic Go feature, and the switch from a pure object-oriented language is not very painful. The Composite design pattern makes our structures predictable but also allows us to create most of the design patterns as we will see in later chapters.
3.21.248.162