Head First Design Patterns using Go — 1. Welcome to Design Patterns: the Strategy Pattern

A descriptive series on the design patterns based on the O’Reily book of the same name adapted to Go from Java

Table of Contents

0. Introduction

  1. Welcome to Design Patterns: the Strategy Pattern
  2. Keeping your Objects in the know: the Observer Pattern
  3. Decorating Objects: the Decorator Pattern
  4. Baking with OO goodness: the Factory Pattern
  5. One of a Kind Objects: the Singleton Pattern
  6. Encapsulating Invocation: the Command Pattern
  7. Being Adaptive: the Adapter and Facade Patterns
  8. Encapsulating Algorithms: the Template Method Pattern
  9. Well-managed Collections: the Iterator and Composite Patterns
  10. The State of Things: the State Pattern
  11. Controlling Object Access: the Proxy Pattern
  12. Patterns of Patterns: Compound Patterns

This series of articles will run you by the design patterns the way it is explained in the Head First Design Patterns book using Go as the coding language.

Problem Statement 1:

  • Develop a game, SimUDuck app which shows a large variety of duck species swimming and making quacking sounds.

Solution:

  • Standard OO techniques and created one Duck superclass from which all other duck types inherit.

Problem Statement 2:

  • Ducks need to FLY.

Solution:

  • Add a fly() method in the Duck class and then all the ducks will inherit it.

Problem Statement 3:

  • Rubber Duckies are flying around the screen.
  • Not all subclasses of Duck have the flying behaviour.
  • Neither do all quack.
  • What was thought as a great use of inheritance for the purpose of reuse hasn’t turned out so well when it comes to maintenance.

Solution:

  • Override the fly() and quack() methods in the RubberDuck class.

Problem Statement 4:

  • What happens when we add wooden decoy ducks? They don’t fly or quack.
  • Runtime behaviour changes are difficult.
  • Hard to gain knowledge of all duck behaviours.
  • Changes can unintentionally affect other ducks.

Solution:

  • Bring in Interface : take the fly() behaviour out of the Duck superclass make a Flyable() interface with a fly() method.
  • That way, only the ducks that are supposed to fly will implement that interface and have a fly() method
  • Same goes for quack() behaviour

Problem Statement 5:

  • Code is duplicated across subclasses — maintenance nightmare.
  • There can be more than one kind of flying behaviour even among the ducks that do fly.

Solution:

  • Design Principle 1: Identify the aspects of your application that vary and separate them from what stays the same.
  • Take what varies and “encapsulate” it so it won’t affect the rest of your code.
  • Result? Fewer unintended consequences from code changes and more flexibility in your systems
  • Looking at the Duck class, other than problems with fly() and quack(), it is working well and there are no other parts that appear to vary or change frequently.
  • To separate these behaviours from the Duck class, we’ll pull both methods out of the Duck class and create a new set of classes to represent each behaviour. For instance, we might have one class that implements quacking, another that implements squeaking, and another that implements silence
  • Design Principle 2: Program to an interface, not an implementation.
  • The Duck subclasses will use a behaviour represented by an interface (FlyBehaviour and QuackBehaviour), so that the actual implementation of the behaviour (in other words, the specific concrete behaviour coded in the class that implements the FlyBehaviour or QuackBehaviour) won’t be locked into the Duck subclass.
  • That way, the Duck classes won’t need to know any of the implementation details for their own behaviours.
  • Benefits of this design : a) code reusability, b) new behaviours can be added without modifying any of our existing behaviour classes or touching any of the Duck classes
Final UML Diagram
  • Design Principle 3: Favour composition (HAS-A relationship) over inheritance (IS-A relationship)
  • Gives more flexibility, can change behaviour/algorithm at runtime
  • Congratulations on learning the first design pattern : the STRATEGY pattern
  • Wiki Definition: 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.

Show me the code:

  • FlyBehaviour set of classes
package main

import "fmt"

/**
* Interface that all flying behaviour classes implement
*/

type flyBehaviour interface {
fly()
}

/**
* Flying behaviour implementation for ducks that fly
*/

type flyWithWings struct{}

func (fw *flyWithWings) fly() {
fmt.Println("I'm flying")
}

/**
* Flying behaviour implementation for ducks
* that do not fly (like rubber and decoy ducks)
*/

type flyNoWay struct{}

func (fnw *flyNoWay) fly() {
fmt.Println("I can't fly")
}
  • QuackBehaviour set of classes
package main

import "fmt"

type quackBehaviour interface {
quack()
}

type quack struct{}

func (q *quack) quack() {
fmt.Println("Quack")
}

type muteQuack struct{}

func (mq *muteQuack) quack() {
fmt.Println("Silence")
}

type squeak struct{}

func (s *squeak) quack() {
fmt.Println("Squeak")
}
  • Duck Abstract Class

Refer this to know how to implement Abstract Classes in Go

package main

import "fmt"

type abstractDuck struct {
display func()
/**
* Declare two reference variables for the behaviour interface types.
* All duck subclasses inherit these
*/

flyingBehaviour flyBehaviour
quackingBehaviour quackBehaviour
}

/**
* Delegate fly and quack behaviour to
* respective behaviour classes
*/

func (d *abstractDuck) performFly() {
d.flyingBehaviour.fly()
}

func (d *abstractDuck) performQuack() {
d.quackingBehaviour.quack()
}

func (d *abstractDuck) swim() {
fmt.Println("All ducks float, even decoys!")
}

/**
* Setting the behaviour dynamically
*/

func (d *abstractDuck) setFlyingBehaviour(fb flyBehaviour) {
d.flyingBehaviour = fb
}

func (d *abstractDuck) setQuackingBehaviour(qb quackBehaviour) {
d.quackingBehaviour = qb
}
  • Integrating with Duck subclasses
type mallardDuck struct {
*abstractDuck
}

func newMallardDuck() *mallardDuck {
// implementing the abstract method
d := &abstractDuck{
display: func() {
fmt.Println("I’m a real Mallard duck")
}
,
flyingBehaviour: &flyWithWings{},
quackingBehaviour: &quack{},
}

return &mallardDuck{d}
}

type modelDuck struct {
*abstractDuck
}

func newModelDuck() *modelDuck {
// implementing the abstract method
d := &abstractDuck{
display: func() {
fmt.Println("I’m a real Model duck")
}
,
flyingBehaviour: &flyNoWay{},
quackingBehaviour: &muteQuack{},
}

return &modelDuck{d}
}
  • Testing the Duck Code
package mainfunc main() {
mallardDuck := newMallardDuck()
mallardDuck.display()
mallardDuck.performFly()
mallardDuck.performQuack()
mallardDuck.swim()
modelDuck := newModelDuck() modelDuck.display()
modelDuck.performFly()
modelDuck.performQuack()
modelDuck.swim()
}
  • Run the code!
I’m a real Mallard duck
I'm flying
Quack
All ducks float, even decoys!
I’m a real Model duck
I can't fly
Silence
All ducks float, even decoys!
  • Creating new FlyBehaviour
/**
* Rocket-powered flying behaviour
*/

type flyRocketPowered struct{}

func (frp *flyRocketPowered) fly() {
fmt.Println("I’m flying with a rocket!")
}
  • Changing behaviour dynamically
package main

import "fmt"

func main() {
mallardDuck := newMallardDuck()

mallardDuck.display()
mallardDuck.performFly()
mallardDuck.performQuack()
mallardDuck.swim()

modelDuck := newModelDuck()

modelDuck.display()
modelDuck.performFly()
modelDuck.performQuack()
modelDuck.swim()

modelDuck.setFlyingBehaviour(&flyRocketPowered{})
fmt.Print("New Flying Behaviour of Model Duck: ")
modelDuck.performFly()

}
  • Run it!
I’m a real Mallard duck
I'm flying
Quack
All ducks float, even decoys!
I’m a real Model duck
I can't fly
Silence
All ducks float, even decoys!
New Flying Behaviour of Model Duck: I’m flying with a rocket!

BIG PICTURE

  • Use the Strategy pattern when you want to use different variants of an algorithm within an object and be able to switch from one algorithm to another during runtime.
  • Use the Strategy when you have a lot of similar classes that only differ in the way they execute some behaviour.
  • Use the pattern when your class has a massive conditional operator that switches between different variants of the same algorithm.

Engineering leader @ Myntra (India’s largest Fashion e-store) | Ex-Flipkart | 5AM Club Member | Searching for my next adventure.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store