Skip to content

One of the most resourceful implementations of design patterns

Notifications You must be signed in to change notification settings

proxyserver2023/design-patterns

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

80 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Design Patterns

One of the most resourceful implementations of design patterns. I tried to use more words to make it more verbose, so that anyone can pick the concepts. But primarily languages used are Golang, Python

TOC

Creational Patterns

Abstract Factory

Creates an instance of several families of classes.

Builder

Separates object construction from its representation.

Factory

  • Create an instance of several derived classes.
  • Define an interface for creating an object, but let subclasses decide which class to instantiate.
  • Factory Method lets a class defer instantiation to subclasses.

Class Diagram

Sourcemaking-Factory-Pattern-Image

Sourcemaking-Factory-Pattern-Image-Implementation

Example

factory-pattern

Notes

  • Make all implemented constructors private or protected.

Object Pool

  • Avoid expensive acquisition and release of resources by recycling objects.
  • Significant Performance Boost
  • Used where
    • cost of initializing a class instance is high,
    • the rate of instantiation of a class is high,
    • the number of instantiation in use at any one time is low.
  • Object Caching
  • A.K.A resource pool
  • the pool will be a growing pool.
  • we can restricts the number of objects created.
  • It is desirable to keep all Reusable objects that are not currently in use in the same object pool so that they can be managed by one coherent policy. To achieve this, the Reusable Pool class is designed to be a singleton class.
  • we don't want a process to have to wait for a particular object to be released, so the Object Pool also instantiates new objects as they are required, but must also implement a facility to clean up unused objects periodically.

UML Class Diagram

connection-pool

connection-pool

Credits - SourceMaking

Example

type Reusable struct{}

type ReusablePool struct {
	Objects []Reusable
	MaxPoolSize int
}

func (r *ReusablePool) Acquire() Reusable{

	if r.Objects == nil {
		r.Objects = make([]Reusable, MaxPoolSize)
	}

	r.Objects = r.Objects[1:]
}

func (r *ReusablePool) Release(re *Reusable) {

}


func main() {
	reusablePool := &ReusablePool{MaxPoolSize: 10}
	reusable := reusablePool.Acquire()
	reusablePool.Release(reusable)
}

Prototype

Prototype Pattern

The Prototype Pattern creates duplicate objects while keeping performance in mind.

  • It requires implementing a prototype interface which tells to create a clone of the current object.
  • It is used when creation of object directly is costly.

For instance, an object is to be created after a costly database operation. We can cache the object, returns its clone on next request and update the database as and when needed thus reducing the database calls.

Example - 1: generate different configuration files depending on our needs

package configurer

type Config struct {
	workDir string
	user string
}


func NewConfig(user string, workDir string) Config {
	return Config{
		user: user,
		workDir: workDir,
	}
}

func (c Config) WithUser(user string) Config {
	c.user = user
	return c
}

func (c Config) WithWorkDir(workDir string) Config {
	c.workDir = workDir
	return c
}

We want to be able to mutate the object without affecting its initial instance. The goal is to be able to generate different configuration files without loosing the flexibility of customizing them without mutation of the initial default configuration.

Singleton

  • only one instance
  • global point to access the instance
  • initialization on first use

If app needs one and only one instance of an object.

type privateStructure struct {
	value string
}

var singleVariable privateStructure

func GetSingletonInstance() privateStructure {
	if singleVariable != nil {
		return singleVariable
	}

	singleVariable = privateStructure{
		value: "some data",
	}

	return singleVariable
}

A thread-safe solution might be

var mu sync.Mutex

func GetInstance() *singleton {
	mu.Lock()
	defer mu.Unlock()

	if instance == nil {
		instance = &singleton{}
	}

	return instance

}

Check-Lock-Check Pattern

func GetInstance() *singleton {

	if instance == nil {
		mu.Lok()
		defer mu.Unlock()

		if instance == nil {
			instance = &singleton{}
		}
	}

	return instance
}

But using the sync/atomic package, we can atomically load and set a flag that will indicate if we have initialized or not our instance.

import sync
import sync/atomic

var initialized uint32

func Getinstance() *singleton{
	if atomic.LoadUInt32(&initialized) == 1 {
		return instance
	}

	mu.Lock()
	defer mu.Unlock()

	if initialized == 0 {
		instance = &singleton{}
		atomic.StoreUint32(&initialized, 1)
	}
}

Idiomatic singleton approach in go

package singleton

import (
	"sync"
)


type singleton struct {}

var instance *singleton
var once sync.Once

func GetInstance() *singleton {
	once.Do(func(){
		instance = &singleton
	})
	return instance
}

Example Code

package main

import (
	"fmt"

	"github.com/alamin-mahamud/go-design-patterns/singleton"
)

func main() {
	s := singleton.GetInstance()
	s.Data = 1
	fmt.Println("1st -> ", s.Data)

	s2 := singleton.GetInstance()
	fmt.Println("2nd -> ", s2.Data)

	s3 := singleton.GetInstance()
	fmt.Println("3rd -> ", s3.Data)

	s2.Data = 20
	fmt.Println("1st -> ", s.Data)
	fmt.Println("2nd -> ", s2.Data)
	fmt.Println("3rd -> ", s3.Data)

	s3.Data = 10
	fmt.Println("1st -> ", s.Data)
	fmt.Println("2nd -> ", s2.Data)
	fmt.Println("3rd -> ", s3.Data)

}
///////////////////////////////////////////
// 1st ->  1
// 2nd ->  1
// 3rd ->  1
// 1st ->  20
// 2nd ->  20
// 3rd ->  20
// 1st ->  10
// 2nd ->  10
// 3rd ->  10
////////////////////////////////////////////

Structural Patterns

Ease the design by identifying a simple way to realize relationships between entities.

Match interfaces of different classes.

type-c-adapter

We can use type-c adapter for using our traditional usb-2, usb-3, micro-SD, ethernet devices. They are not same. We used to have different ports for these devices.

But this adapter let's us work with common ground.

UML class diagram

Example

type RowingBoat interface{
	row()
}


type FishingBoat struct {}
func (f *FishingBoat) sail() {}


type Captain struct {}
func (c *Captain) row(){}


type FishingBoatAdapter struct {
	fishingBoat FishingBoat
}

func (f *FishingBoatAdapter) row() {
	boat.sail()
}
type TwoPinCharger interface {
	TwoPinCharge()
}

type TwoPinChargerMobile struct {}
func (t *TwoPinChargerMobile) TwoPinCharge() {}


type ThreePinCharger interface {
	ThreePinCharge()
}
func (t *ThreePinChargerMobile) ThreePinCharge() {}

type TwoPin2ThreePinChargerAdapter struct {
	twoPinCharger TwoPinCharger
}
func (t *TwoPin2ThreePinChargerAdapter) ThreePinCharge() {
	twoPinCharger.TwoPinCharge()
}

// TODO: fix implementation using Class Diagram
// To use the adapter
twoPinCharger := &TwoPinChargerMobile{}
adapter := &TwoPin2ThreePinChargeAdapter{twoPinCharger}
adapter.ThreePinCharge()

Separate an object's interface from it's implementation

A tree structure of simple and composite objects.

Add responsibilities to objects dynamically.

A single class that represents an entire subsystem.

A fine-grained instance used for efficient sharing.

Restricts accessor/mutator access.

An object representing another object.

Behavioural Patterns

A way of passing a request to a chain of objects.

Encapsulates a command request as an object

A way to include language elements in a program.

Sequentially access the elements of a collection.

Defines simplified communication between classes.

Capture and restore an object's internal state.

Designed to act as a default value of an object.

A way of notifying change to a number of classes.

Alter an object's behaviour when it's state change.

Encapsulates an algorithm inside a class.

Defer the exact steps of an algorithm to a subclass.

Defines a new operation to a class without change.

Microservices

Circuit Breaker

Publish Subscribe

[Placeholder ...]

Service Registry

[Placeholder ...]

Others

Add Property on the fly.

Allow new functions to be added to existing class hierarchies without affecting those hierarchies, and without creating the troublesome dependency cycles that are inherent to the GOF VISITOR Pattern.

UML Class Diagram

acyclic-visitor

Examples

// TODO: implementaion

AntiPatterns

Commonly occuring solutions to a problem, that generates decidedly negative consequences.

When it happens

When Manager or Developer apply good design patterns into wrong context.

How to mitigate

Antipatterns provide a detailed plan to reverse the situation.

Types

  • Software Development Antipatterns
  • Software Architecture Antipatterns
  • Software Product Management Antipatterns

Resources

Credits