Design Pattern: Strategy Pattern in Go

Strategy pattern is used to decide between choosing an algorithm from a bunch of algorithms (that serve the same purpose). This pattern encapsulates the complexity and makes is easier for the client to choose and use an algorithm.

This article demonstrates Strategy pattern implementations in Go. Check the following examples.

Implementation

Here is a simple Implementation of the Strategy pattern-

// Strategy pattern implementation in GoLang

package main

import "fmt"

// Strategy interface
type IStrategy interface {
	operation1()
	operation2()
}

// First concrete strategy
type ConcreteStrategyOne struct {
}

func NewConcreteStrategyOne() (strategy *ConcreteStrategyOne) {
	strategy = &ConcreteStrategyOne{}
	return
}

func (strategy *ConcreteStrategyOne) operation1() {
	fmt.Println("Strategy One: operation 1")
}

func (strategy *ConcreteStrategyOne) operation2() {
	fmt.Println("Strategy One: operation 2")
}

// Second concrete strategy
type ConcreteStrategyTwo struct {
}

func NewConcreteStrategyTwo() (strategy *ConcreteStrategyOne) {
	strategy = &ConcreteStrategyOne{}
	return
}

func (strategy *ConcreteStrategyTwo) operation1() {
	fmt.Println("Strategy Two: operation 1")
}

func (strategy *ConcreteStrategyTwo) operation2() {
	fmt.Println("Strategy Two: operation 2")
}

// Strategy class
type Strategy struct {
	strategy IStrategy
}

func NewStrategy(strategy IStrategy) (newStrategy *Strategy) {
	newStrategy = &Strategy{}
	newStrategy.strategy = strategy
	return
}

func (strategy *Strategy) someOperation1() {
	// perform some operation as per requirement (if required)

	strategy.strategy.operation1()

	// perform some operation as per requirement (if required)
}

func (strategy *Strategy) someOperation2() {
	// perform some operation as per requirement (if required)

	strategy.strategy.operation2()

	// perform some operation as per requirement (if required)
}

func (strategy *Strategy) someComplexOperation() {
	// perform some operation as per requirement (if required)

	strategy.strategy.operation2()

	// perform some operation as per requirement (if required)

	strategy.strategy.operation1()

	// perform some operation as per requirement (if required)
}

// Demo
func main() {
	fmt.Println("Using Strategy One")

	strategy := NewStrategy(NewConcreteStrategyOne())
	strategy.someOperation1()

	fmt.Println("Using Strategy Two")

	strategy = NewStrategy(NewConcreteStrategyTwo())
	strategy.someOperation1()
	strategy.someOperation2()
	strategy.someComplexOperation()
}

Output will be as below-

Using Strategy One
Strategy One: operation 1

Using Strategy Two
Strategy One: operation 1
Strategy One: operation 2
Strategy One: operation 2
Strategy One: operation 1

Examples

Here are a few examples of the Strategy pattern implementation-

Example #1: Transport

Let’s take the example of a transport system.

Transport Interface

// transport.go

package main

type Transport interface {
	GetInfo()
	Operate()
	Repair()
	Start()
	Stop()
}

Bike Struct

// bike.go

package main

import "fmt"

type Bike struct {
}

func NewBike() (bike *Bike) {
	bike = &Bike{}
	return
}

func (bike *Bike) GetInfo() {
	fmt.Println("Transport type: Bike")
	fmt.Println("Number of wheels: 2")
	fmt.Println("Average Weight: 700 Pounds")
}

func (bike *Bike) Operate() {
	fmt.Println("Riding Bike ............")
}

func (bike *Bike) Repair() {
	fmt.Println("Bike repair")
}

func (bike *Bike) Start() {
	fmt.Println("Bike started")
}

func (bike *Bike) Stop() {
	fmt.Println("Bike stopped")
}

Car Struct

// car.go

package main

import "fmt"

type Car struct {
}

func NewCar() (car *Car) {
	car = &Car{}
	return
}

func (car *Car) GetInfo() {
	fmt.Println("Transport type: Car")
	fmt.Println("Number of wheels: 4")
	fmt.Println("Average Weight: 4,000 Pounds")
}

func (car *Car) Operate() {
	fmt.Println("Driving car ............")
}

func (car *Car) Repair() {
	fmt.Println("Car repair")
}

func (car *Car) Start() {
	fmt.Println("Car started")
}

func (car *Car) Stop() {
	fmt.Println("Car stopped")
}

Plane Struct

// plane.go

package main

import "fmt"

type Plane struct {
}

func NewPlane() (plane *Plane) {
	plane = &Plane{}
	return
}

func (plane *Plane) GetInfo() {
	fmt.Println("Transport type: Plane")
	fmt.Println("Number of wheels: 3")
	fmt.Println("Average Weight: 50,000 Pounds")
}

func (plane *Plane) Operate() {
	fmt.Println("Flying plane ............")
}

func (plane *Plane) Repair() {
	fmt.Println("Plane repair")
}

func (plane *Plane) Start() {
	fmt.Println("Plane started")
}

func (plane *Plane) Stop() {
	fmt.Println("Plane stopped")
}

Transport Strategy Struct

// transport_strategy.go

package main

type TransportStrategy struct {
	transport Transport
}

func NewTransportStrategy(transport Transport) (rcvr *TransportStrategy) {
	rcvr = &TransportStrategy{}
	rcvr.transport = transport
	return
}

func (rcvr *TransportStrategy) Execute() {
	rcvr.transport.Start()
	rcvr.transport.GetInfo()
	rcvr.transport.Operate()
}

func (rcvr *TransportStrategy) Repair() {
	rcvr.transport.Repair()
}

func (rcvr *TransportStrategy) Stop() {
	rcvr.transport.Stop()
}

Demo

// main.go

package main

import "fmt"

func main() {
	fmt.Println("Operating Bike:")

	myTransport := NewTransportStrategy(NewBike())
	myTransport.Execute()
	myTransport.Stop()

	fmt.Println("\n\nOperating Car:")

	myTransport = NewTransportStrategy(NewCar())
	myTransport.Execute()
	myTransport.Stop()
	myTransport.Repair()

	fmt.Println("\n\nOperating plane:")

	myTransport = NewTransportStrategy(NewPlane())
	myTransport.Execute()
	myTransport.Stop()
}

Output

Operating Bike:
Bike started
Transport type: Bike
Number of wheels: 2
Average Weight: 700 Pounds
Riding Bike ............
Bike stopped


Operating Car:
Car started
Transport type: Car
Number of wheels: 4
Average Weight: 4,000 Pounds
Driving car ............
Car stopped
Car repair


Operating plane:
Plane started
Transport type: Plane
Number of wheels: 3
Average Weight: 50,000 Pounds
Flying plane ............
Plane stopped

Example #2: Mathematical Calculations

Calculation Interface

// calculation.go

package main

type Calculation interface {
	Execute(num1 float32, num2 float32) (float32)
}

Add Calculation Struct

// add.go

package main

type Add struct {}

func NewAdd() (add *Add) {
	add = &Add{}
	return
}

func (add *Add) Execute(num1 float32, num2 float32) (float32) {
	return num1 + num2
}

Subtract Calculation Struct

// subtract.go

package main

type Subtract struct {}

func NewSubtract() (subtract *Subtract) {
	subtract = &Subtract{}
	return
}

func (subtract *Subtract) Execute(num1 float32, num2 float32) (float32) {
	return num1 - num2
}

Multiply Calculation Struct

// multiply.go

package main

type Multiply struct {}

func NewMultiply() (rcvr *Multiply) {
	rcvr = &Multiply{}
	return
}

func (rcvr *Multiply) Execute(num1 float32, num2 float32) (float32) {
	return num1 * num2
}

Divide Calculation Struct

// divide.go

package main


type Divide struct {}

func NewDivide() (divide *Divide) {
	divide = &Divide{}
	return
}

func (divide *Divide) Execute(num1 float32, num2 float32) (float32) {
	return num1 / num2
}

Calculation Strategy Struct

// calculation_strategy.go

package main

type CalculationStrategy struct {
	calculation Calculation
}

func NewCalculationStrategy(calculation Calculation) (calculationStrategy *CalculationStrategy) {
	calculationStrategy = &CalculationStrategy{}
	calculationStrategy.calculation = calculation
	return
}

func (calculationStrategy *CalculationStrategy) Execute(num1 float32, num2 float32) (float32) {
	return calculationStrategy.calculation.Execute(num1, num2)
}

Demo

// main.go

package main

import "fmt"

func main() {
	fmt.Println("Performing operation: ADD")

	myCalculation := NewCalculationStrategy(NewAdd())
	result := myCalculation.Execute(10, 5)

	fmt.Printf("10 + 5 = %v\n", result)

	fmt.Println("\n\nPerforming operation: SUBTRACT")

	myCalculation = NewCalculationStrategy(NewSubtract())
	result = myCalculation.Execute(10, 5)

	fmt.Printf("10 - 5 = %v\n", result)

	fmt.Println("\n\nPerforming operation: MULTIPLY")

	myCalculation = NewCalculationStrategy(NewMultiply())
	result = myCalculation.Execute(10, 5)

	fmt.Printf("10 * 5 = %v\n", result)

	fmt.Println("\n\nPerforming operation: DIVIDE")

	myCalculation = NewCalculationStrategy(NewDivide())
	result = myCalculation.Execute(10, 5)

	fmt.Printf("10 / 5 = %v\n", result)
}

Result

Performing operation: ADD
10 + 5 = 15


Performing operation: SUBTRACT
10 - 5 = 5


Performing operation: MULTIPLY
10 * 5 = 50


Performing operation: DIVIDE
10 / 5 = 2

Example #3: File Storage

Storage Interface

// storage.go

package main

type Storage interface {
	PrintFileInfo(fileId int)
	RetrieveFile(fileId int) string
	StoreFile(tempPath string) int
}

Local Storage Struct

// local_storage.go

package main

import (
	"fmt"
	"math/rand"
)

type LocalStorage struct {
}

func NewLocalStorage() (localStorage *LocalStorage) {
	localStorage = &LocalStorage{}
	return
}

func (localStorage *LocalStorage) PrintFileInfo(fileId int) {
	fmt.Println("Storage type: Local Storage")
	fmt.Printf("File ID: %d\n", fileId)
	fmt.Printf("File URL: %s\n", localStorage.RetrieveFile(fileId))
}

func (localStorage *LocalStorage) RetrieveFile(fileId int) string {
	// Retrieve the url and return back

	// Some dummy url is returned for demo purpose
	return fmt.Sprintf("https://bigboxcode.com/files/local/%d", fileId)
}

func (localStorage *LocalStorage) StoreFile(tempPath string) int {
	// Code to store file here

	// return the ID that is obtained from the database record or any other unique identifier.

	// For demo purpose a random number is returned here
	return rand.Intn(100)
}

Google Drive Storage Struct

// google_drive_storage.go

package main

import (
	"fmt"
	"math/rand"
)

type GoogleDriveStorage struct {
}

func NewGoogleDriveStorage() (googleDriveStorage *GoogleDriveStorage) {
	googleDriveStorage = &GoogleDriveStorage{}
	return
}

func (googleDriveStorage *GoogleDriveStorage) PrintFileInfo(fileId int) {
	fmt.Println("Storage type: Google Drive")
	fmt.Printf("File ID: %d\n", fileId)
	fmt.Printf("File URL: %s\n", googleDriveStorage.RetrieveFile(fileId))
}

func (googleDriveStorage *GoogleDriveStorage) RetrieveFile(fileId int) string {
	// Retrieve the url and return back

	// Some dummy url is returned for demo purpose
	return "https://drive.google.com/file/d/1234_9K7654hu6RT_9j7JKY3fK/view"
}

func (googleDriveStorage *GoogleDriveStorage) StoreFile(tempPath string) int {
	// Code to store file here

	// return the ID that is obtained from the database record or any other unique identifier.

	// For demo purpose a random number is returned here
	return rand.Intn(1000)
}

AWS S3 Storage Struct

// s3_storage.go

package main

import (
	"fmt"
	"math/rand"
)

type S3Storage struct {
}

func NewS3Storage() (s3Storage *S3Storage) {
	s3Storage = &S3Storage{}
	return
}

func (s3Storage *S3Storage) PrintFileInfo(fileId int) {
	fmt.Println("Storage type: AWS S3")
	fmt.Printf("File ID: %d\n", fileId)
	fmt.Printf("File URL: %s\n", s3Storage.RetrieveFile(fileId))
}

func (s3Storage *S3Storage) RetrieveFile(fileId int) string {
	// Retrieve the url and return back

	// Some dummy url is returned for demo purpose
	return "https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf"
}

func (s3Storage *S3Storage) StoreFile(tempPath string) int {
	// Code to store file here

	// return the ID that is obtained from the database record or any other unique identifier.

	// For demo purpose a random number is returned here
	return rand.Intn(10000)
}

Storage Strategy Struct

// storage_strategy.go

package main

type StorageStrategy struct {
	storage Storage
}

func NewStorageStrategy(storage Storage) (storageStrategy *StorageStrategy) {
	storageStrategy = &StorageStrategy{}
	storageStrategy.storage = storage
	return
}

func (storageStrategy *StorageStrategy) GetFileUrl(fileId int) string {
	return storageStrategy.storage.RetrieveFile(fileId)
}

func (storageStrategy *StorageStrategy) UploadFile(tempPath string) int {
	fileId := storageStrategy.storage.StoreFile(tempPath)
	storageStrategy.storage.PrintFileInfo(fileId)
	return fileId
}

Demo

// main.go

package main

import "fmt"

func main() {
	 // Use Local storage
	fmt.Println("Using local storage:")
	
	fileStorage := NewStorageStrategy(NewLocalStorage())
	fileStorage.UploadFile("/some-temp-path")

	// Use car
	fmt.Println("\n\nUsing AWS S3:")

	fileStorage = NewStorageStrategy(NewS3Storage())
	fileStorage.UploadFile("/some-temp-path")

	// Use plane
	fmt.Println("\n\nUsing Google Drive:")

	fileStorage = NewStorageStrategy(NewGoogleDriveStorage())
	fileStorage.UploadFile("/some-temp-path")
}

Output

Using local storage:
Storage type: Local Storage
File ID: 29
File URL: https://bigboxcode.com/files/local/29


Using AWS S3:
Storage type: AWS S3
File ID: 1437
File URL: https://bigboxcode.s3.amazonaws.com/pdf/UC-0e7654338-5697-4f99-b33-d89h87g5gf4gwfg.pdf


Using Google Drive:
Storage type: Google Drive
File ID: 68

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check Strategy pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.