Strategy Pattern in Golang
Usually I find technical books very dry, but Head First Design Patterns was awash with funny anecdotes and eye-grabbing illustrations. It felt a good place to start learning design patterns though it may miss out on more intricate details. The code examples are in Java. I recently started learning Go and chose it to implement the strategy pattern.
Strategy Pattern
According to the book,
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
Okay, that did not make much sense to me at first too. Let’s try to understand through the problem statement.
The problem statement
We are told to design a duck pond simulation where different species of ducks exist. They can have a variety of swimming styles, flying abilities and sounds.
Requirements keep on changing, but finally we have four types of ducks.
- Which is actually a kind of duck species.
- It can fly.
- It makes a quack sound.
- It can float.
2. Redhead Duck:
- Which is actually a kind of duck species.
- It can fly.
- It makes a quack sound.
- It can float.
3. Rubber Duck:
- Which is the kind of yellow rubber ducks we see in bathrooms!
- It can NOT fly.
- It makes a squeaking sound.
- It can float.
4. Decoy Duck:
- Which is the kind of wooden ducks showcased in your living rooms!
- It can NOT fly.
- It does NOT make any sound.
- It can float.
Thought process
- Our initial instinct is to make a class of
Duck
, which has the functions offly
,quack
andswim
. - Subclasses
Mallard
,Redhead
,Rubber
andDecoy
can inherit the behaviours from Duck. Rubber
andDecoy
will override the default fly and quack behaviours to do nothing. Which do not make much sense!- We change our approach to have
Flyable
andQuackable
interfaces, which the individual subclasses may choose to use. - We can implement the behaviours, inside the respective classes, but that will lead to repetition. e.g. Both
Rubber
andDecoy
ducks cannot fly. So, it is better to have aFlyWithWings
class (forMallard
andRedhead
) andFlyNoWay
(forRubber
andDecoy
) implement theFlyable
interface. - Similarly, for
Quackable
interface we make classes based on types of sound made. - The final solution (shown below) can appear over-engineered for the simple use case but helps a lot in flexibility for more bigger applications.
- To enable switching behaviours at run-time (dynamic dispatch), we add two setters
setQuacker
andsetFlyer
to the Duck class. - In the book, a new type of Duck is introduced whose flight is powered by a rocket.
- I took creative liberty, to instead have Donald Duck, the cartoon character. As far as I remember he can’t fly despite having 2 wings (hands?). He mutters “Aw, phooey!”, in his quintessential tone.
UML Class Diagram
FlyBehaviour
has been renamed to Flyer
; likewise for QuackBehaviour
.
Language specifics
Golang is not a pure object-oriented programming language. However, we can use the language constructs to implement strategy pattern and can benefit from it.
There is no concept of class. A struct
can have fields (data members). Interface can declare functions which need to be implemented by the struct.
Setter is NOT considered an idiomatic Go approach. In the code, I have shown use of both a setter and a direct member access of a public field.
The Code
The output of the following code is
I'm a duck.
utils.Duck{flyer:utils.Flyer(nil), Quacker:utils.Quacker(nil)}
utils.Duck{flyer:utils.FlyNoWay{NoOfWings:0}, Quacker:utils.MuteQuack{}}
<< Silence >>
utils.Duck{flyer:utils.FlyNoWay{NoOfWings:2}, Quacker:utils.Speak{Speech:"Aw, phooey!"}}
Aw, phooey!
The complete code can be found in my Github repository.
Takeaways
This pattern “Favours composition over inheritance” which is in alignment with Golang’s philosophy (inheritance is not supported).
Some interesting design principles which came up
Identify the aspects of your application that vary and separate them from what stays the same.
Program to an interface, not an implementation.