Design Patterns in Golang - Part-1

Published 10-03-2017 00:00:00

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)
	}
}