Introduction
Recently I started a series of articles about Gang of Four Design Patterns and their adoption in Golang. They made a lot of noise in the community. I read a lot of contradictionary opionions whether should be used or not. I am publishing those articles as show case how the common design patterns can be adopted and implemented in Golang. I don’t encourage or promote their usage. Every developer has own style of programming, architecture desing and problem solving solutions.
The Design Patterns have never been something encourage by the Golang community. They are not idiomatic for the language. Everybody knows that the language itself is very opioninated and idiomatic. There are no so many ways to achieve particular task or solve particular problem.
Creational patterns
Creational design patterns abstract the instantiation process. They help make a system independent of how its objects are created, composed, and represented.
Builder Pattern
Builder pattern separates the construction of a complex object from its representation so that the same construction process can create different representations.
In Go, normally a configuration struct is used to achieve the same behavior,
however passing a struct to the builder method fills the code with boilerplate
if cfg.Field != nil {...}
checks.
Implementation
package car
type Speed float64
const (
MPH Speed = 1
KPH = 1.60934
)
type Color string
const (
BlueColor Color = "blue"
GreenColor = "green"
RedColor = "red"
)
type Wheels string
const (
SportsWheels Wheels = "sports"
SteelWheels = "steel"
)
type Builder interface {
Color(Color) Builder
Wheels(Wheels) Builder
TopSpeed(Speed) Builder
Build() Interface
}
type Interface interface {
Drive() error
Stop() error
}
Usage
assembly := car.NewBuilder().Paint(car.RedColor)
familyCar := assembly.Wheels(car.SportsWheels).TopSpeed(50 * car.MPH).Build()
familyCar.Drive()
sportsCar := assembly.Wheels(car.SteelWheels).TopSpeed(150 * car.MPH).Build()
sportsCar.Drive()
Factory Method Pattern
Factory method creational design pattern allows creating objects without having to specify the exact type of the object that will be created.
Implementation
The example implementation shows how to provide a data store with different backends such as in-memory, disk storage.
Types
package data
import "io"
type Store interface {
Open(string) (io.ReadWriteCloser, error)
}
Different Implementations
package data
type StorageType int
const (
DiskStorage StorageType = 1 << iota
TempStorage
MemoryStorage
)
func NewStore(t StorageType) Store {
switch t {
case MemoryStorage:
return newMemoryStorage( /*...*/ )
case DiskStorage:
return newDiskStorage( /*...*/ )
default:
return newTempStorage( /*...*/ )
}
}
Usage
With the factory method, the user can specify the type of storage they want.
s, _ := data.NewStore(data.MemoryStorage)
f, _ := s.Open("file")
n, _ := f.Write([]byte("data"))
defer f.Close()
Object Pool Pattern
The object pool creational design pattern is used to prepare and keep multiple instances according to the demand expectation.
Implementation
package pool
type Pool chan *Object
func New(total int) *Pool {
p := make(Pool, total)
for i := 0; i < total; i++ {
p <- new(Object)
}
return &p
}
Usage
Given below is a simple lifecycle example on an object pool.
p := pool.New(2)
select {
case obj := <-p:
obj.Do( /*...*/ )
p <- obj
default:
// No more objects left — retry later or fail
return
}
Rules of Thumb
- Object pool pattern is useful in cases where object initialization is more expensive than the object maintenance.
- If there are spikes in demand as opposed to a steady demand, the maintenance overhead might overweigh the benefits of an object pool.
- It has positive effects on performance due to objects being initialized beforehand.
Singleton Pattern
Singleton creational design pattern restricts the instantiation of a type to a single object.
Implementation
package singleton
type singleton map[string]string
var (
once sync.Once
instance singleton
)
func New() singleton {
once.Do(func() {
instance = make(singleton)
})
return instance
}
Usage
s := singleton.New()
s["this"] = "that"
s2 := singleton.New()
fmt.Println("This is ", s2["this"])
// This is that
Rules of Thumb
- Singleton pattern represents a global state and most of the time reduces testability.
Abstract Factory
Provide an interface for creating famili es of related or dependent objects without specifying their concrete classes.
package abstract_factory
import (
"fmt"
"errors"
)
//sub factory abstractions
type VehicleFactory interface {
Create(int) (Vehiche, error)
}
type CarFactory struct{}
func (f *CarFactory) Create(vType int) (Vehiche, error) {
switch vType {
case OFFROADER_CAR:
return new(Offroader), nil
case SPORT_CAR:
return new(Sportcar), nill
default:
return nil, errors.New(fmt.Sprintf("Unsupported car vehicle type:%d", vType))
}
}
type BusFactory struct {}
func(f *BusFactory) Create(vType int)(Vehiche, error) {
switch vType {
case CITY_TOUR_BUS:
return new(CityTourBus), nil
case SIMPLE_BUS:
return new(SimpleBus), nil
default:
return nil, errors.New(fmt.Sprintf("Unsupported bus vehicle type:%d", vType))
}
}
//object families............
type Vehiche interface {
WheelCount() int
}
//Car
const (
OFFROADER_CAR = 1
SPORT_CAR = 2
)
type Car interface {
DoorCount() int
}
type Offroader struct{}
func (c Offroader) WheelCount() int {
return 4
}
func (c Offroader) DoorCount() int {
return 4
}
type Sportcar struct{}
func (c Sportcar) WheelCount() int {
return 4
}
func (c Sportcar) DoorCount() int {
return 2
}
//Bus..................
const (
SIMPLE_BUS = 1
CITY_TOUR_BUS = 2
)
type Bus interface {
FloorCount() int
}
type CityTourBus struct{}
func (c CityTourBus) WheelCount() int {
return 6
}
func (c CityTourBus) FloorCount() int {
return 2
}
type SimpleBus struct{}
func (c SimpleBus) WheelCount() int {
return 4
}
func (c SimpleBus) FloorCount() int {
return 1
}
//super factory
const (
CAR = 1
BUS = 2
)
func CreateVehicleFactory(vfType int) (VehicleFactory, error) {
switch vfType {
case CAR:
return new(CarFactory), nil
case BUS:
return new(BusFactory), nil
default:
return nil, errors.New("Unrecognized factory type:%d", vfType)
}
}