Design pattern golang implementation (not perfect)

Posted by linux1880 on Wed, 15 Dec 2021 16:35:17 +0100

I Design pattern principle

1. Principle of single responsibility

A class has one and only one reason for class change, otherwise, the class should be separated

2. Opening and closing principle

Open to extension and closed to modification

3. Richter substitution principle

It must be ensured that the functions of the base class still take effect in its derived classes, and subclasses must be used transparently where the base class can be referenced

4. Reliance inversion principle

High level modules should not rely on low-level modules. Both rely on abstraction. Abstraction should not rely on details. Details should rely on abstraction and interface oriented programming rather than implementation oriented programming

5. Interface isolation principle

Using multiple isolated interfaces is better than using a single interface. It also has another meaning: reduce the coupling between classes. It can be seen that in fact, the design pattern is a software design idea that starts from the large-scale software architecture and is easy to upgrade and maintain. It emphasizes reducing dependence and coupling.

6. Composite Reuse Principle

Try to use composition / aggregation instead of inheritance.

7. Dimitri's law

An entity should interact with other entities as little as possible to make the system functional modules relatively independent.

2, Design pattern classification

1. Create mode

How do I create objects? Separating the creation and use of objects

Factory method model

The creation of a class is deferred to the implementation of a subclass, which determines which object to create

1. Simple factory mode (static factory method mode)

eg: there are few subclasses of extensions, or extensions are not considered. A factory considers producing inter and amd chips, and will not consider producing other chips in the future
uml diagram:

//Simple factory mode
package main

import "fmt"

type AbstractFun interface {
   Create()
}
type InterChip struct { //inter class

}
type AmdChip struct { //amd class
}
type Factory struct {
}

func (interChip InterChip) Create() {
   fmt.Println("interchip have benn created")
}
func (amdChip AmdChip) Create() {
   fmt.Println("amdchip have benn created")
}
func (factory *Factory) MakeChip(typeChip string) AbstractFun {
   if typeChip == "InterChip" {
   	return NewInterChip()
   }
   if typeChip == "AmdChip" {
   	return NewAmdChip()
   }
   return nil
}

func NewInterChip() *InterChip {
   interchip := InterChip{}
   interchip.Create()
   return &InterChip{}
}
func NewAmdChip() *AmdChip {
   amdChip := AmdChip{}
   amdChip.Create()
   return &AmdChip{}
}

type User struct { //User class
}

func (user User) Use() {
   factory := Factory{}
   factory.MakeChip("AmdChip")
   factory.MakeChip("InterChip")
}
func main() {
   user := User{}
   user.Use()
}

result:

amdchip have benn created
interchip have benn created

2. Factory method mode

Provides an interface that can create a series of interrelated interfaces without specifying specific classes

 package main

import "fmt"

type AbstractFun interface {
	Create()
}
type InterChip struct { //inter class

}
type AmdChip struct { //amd class
}
type InterChipFactory struct {
}
type AmdChipFactory struct {
}
type AbstractFactory interface {
	MakeChip() AbstractFun
}

func (interChip InterChip) Create() {
	fmt.Println("interchip have benn created")
}
func (amdChip AmdChip) Create() {
	fmt.Println("amdchip have benn created")
}
func (amdChipFactory *AmdChipFactory) MakeChip() AbstractFun {
	return NewAmdChip()
}
func (anterChipFactory *InterChipFactory) MakeChip() AbstractFun {
	return NewInterChip()
}
func NewInterChip() *InterChip {
	interchip := InterChip{}
	interchip.Create()
	return &InterChip{}
}
func NewAmdChip() *AmdChip {
	amdChip := AmdChip{}
	amdChip.Create()
	return &AmdChip{}
}
func Use(abstractFactory AbstractFactory) {
	abstractFactory.MakeChip()
}

type User struct { //User class
}

func (user User) Use() {
	interChipFactory := InterChipFactory{}
	Use(&interChipFactory)
	amdChipFactory := AmdChipFactory{}
	Use(&amdChipFactory)
}
func main() {
	user := User{}
	user.Use()
}


Operation results

interchip have benn created
amdchip have benn created

Singleton mode

Ensure that only one instance exists in the system

1. Hungry Han mode

Class will be instantiated when loaded. It is thread safe, but it is easy to waste memory

package main

import "fmt"

type Singleton struct {
	name string
}

var instance *Singleton

func init() {
	instance = &Singleton{
		name: "hungry man",
	}
}
func newInstance() *Singleton {
	return instance
}
func main() {
	instance1 := newInstance()
	instance2 := newInstance()
	//instance3 := Singleton{
	//	name: "lazy man",
	//}
	if instance1 == instance2 {
		fmt.Println("True")
	} else {
		fmt.Println("False")
	}
}

2. Lazy mode

Load when required, but thread safe

package main

import "fmt"

type Singleton struct {
	name string
}

var instance *Singleton

func newInstance() *Singleton {
	if instance == nil {
		instance = &Singleton{
			name: "Lazy man",
		}
		return instance
	}
	return instance
}
func main() {
	instance1 := newInstance()
	instance2 := newInstance()
	if instance1 == instance2 {
		fmt.Println("True")
	} else {
		fmt.Println("False")
	}
}

Thread safe double check lazy mode

package main

import (
	"fmt"
	"sync"
)

type Singleton struct {
	name string
}

var lock sync.Mutex
var instance *Singleton

func newInstance() *Singleton {
	if instance == nil {
		lock.Lock()
		defer lock.Unlock()
		if instance == nil {
			instance = &Singleton{
				name: "Zhou Jie",
			}
		}
		return instance
	}
	return instance
}
func main() {
	instance1 := newInstance()
	instance2 := newInstance()
	if instance1 == instance2 {
		fmt.Println("True")
	} else {
		fmt.Println("False")
	}
}

Abstract factory pattern

With a series of product families, you can use the abstract factory pattern
uml:

package main

import "fmt"

//Abstract factory
type AbstractFactory interface {
	MakeCpu() Cpu
	MakeMainboard() MainBoard
}
type Cpu interface {
	cpu()
}
type MainBoard interface {
	mainBoard()
}
type AmdCpu struct {
}
type InterCpu struct {
}
type AmdMainboard struct {
}
type InterMainboard struct {
}
type InterFactory struct {
}
type AmdFactory struct {
}

func (amdCpu AmdCpu) cpu() {
	fmt.Println("amd cpu")
}
func (interCpu InterCpu) cpu() {
	fmt.Println("inter cpu")
}
func (amdMainboard AmdMainboard) mainBoard() {
	fmt.Println("amd mainboard")
}
func (interMainboard InterMainboard) mainBoard() {
	fmt.Println("inter mainboard")
}
func (interFactory InterFactory) MakeCpu() Cpu {
	cpu := InterCpu{}
	return cpu
}
func (interFactory InterFactory) MakeMainboard() MainBoard {
	mb := InterMainboard{}
	return mb
}
func (amdFactory AmdFactory) MakeCpu() Cpu {

	cpu := AmdCpu{}
	return cpu
}
func (amdFactory AmdFactory) MakeMainboard() MainBoard {

	mb := AmdMainboard{}
	return mb
}

type ComputerEngineer struct { //Computer engineer
	cpuInstance       Cpu
	mainboardInstance MainBoard
}

func (ce *ComputerEngineer) MakeComputer(af AbstractFactory) {
	ce.PrepareHardWare(af)
	ce.cpuInstance.cpu()
	ce.mainboardInstance.mainBoard()
}
func (ce *ComputerEngineer) PrepareHardWare(af AbstractFactory) {
	ce.cpuInstance = af.MakeCpu()
	ce.mainboardInstance = af.MakeMainboard()
}

type Cilent struct { //Customer class
}

func (C Cilent) Call() {
	ce := ComputerEngineer{}
	var af AbstractFactory
	af = InterFactory{}
	ce.MakeComputer(af)
}
func main() {
	c := Cilent{}
	c.Call()
}

Builder pattern

Prototype mode

Use the prototype instance to specify the type of object to create, and create a new object by copying the prototype object

2. Behavioral model

Observer mode

Defines a one to many dependency between objects. When the state of an object changes, all objects that depend on it are notified and updated automatically.

package main

import "fmt"

type Subject interface {
	Notify()
	AddSniper(observer Observer)
	GetState() string
	SetState(state string)
	DeleteSniper(observer Observer)
}
type Observer interface {
	Updata()
}
type SniperHandle struct {
	SlicesObs []Observer
	state     string
}
type Sniper struct {
	Name string
}

func NewSniper(name string) Sniper {
	return Sniper{
		Name: name,
	}
}
func (snp Sniper) Updata() {
	fmt.Println(snp.Name, "Sniper attention, the enemy appears")
}
func (sh *SniperHandle) Notify() {
	for _, observer := range sh.SlicesObs {
		observer.Updata()
	}
}
func (sh *SniperHandle) SetState(state string) {
	sh.state = state
	sh.Notify()
}
func (sh *SniperHandle) GetState() string {
	return sh.state
}
func (sh *SniperHandle) AddSniper(observer Observer) {
	sh.SlicesObs = append(sh.SlicesObs, observer)
}
func NewSinperHandle(state string, observers ...Observer) SniperHandle {
	SniperHandle := SniperHandle{}
	for _, v := range observers {
		SniperHandle.AddSniper(v)

	}
	SniperHandle.state = state
	return SniperHandle
}

func (sh *SniperHandle) DeleteSniper(observer Observer) {
	var index int
	for i, v := range sh.SlicesObs {
		if observer == v {
			index = i
		}
	}
	sh.SlicesObs = append(sh.SlicesObs[:index], sh.SlicesObs[index+1:]...)
}
func Run(s Subject) {
	s.SetState("The enemy appeared")
	fmt.Println(s.GetState())
}
func main() {
	var sniper1 Observer
	var sniper2 Observer
	var sniper3 Observer
	sniper1 = NewSniper("Curry")
	sniper2 = NewSniper("James")
	sniper3 = NewSniper("Lillard")
	var sniperHandle SniperHandle
	sniperHandle = NewSinperHandle("The enemy did not appear", sniper1, sniper2, sniper3)
	Run(&sniperHandle)

}

result

Kuri sniper attention, the enemy appears
 James sniper attention, the enemy appears
 Lillard sniper attention, the enemy appears
 The enemy appeared

Strategy mode

Intent: define a series of algorithms, encapsulate them one by one, and make them replaceable.

Main solution: when there are many similar algorithms, it is complex and difficult to maintain when using if... else.

When to use: a system has many classes, and what distinguishes them is their direct behavior.

How to solve it: encapsulate these algorithms into classes one by one and replace them arbitrarily.

Key code: implement the same interface.
uml:

package main

import "fmt"

type Strategy interface {
	DoOperation() int
}
type AddOperation struct {
	num1 int
	num2 int
}
type SubOperation struct {
	num1 int
	num2 int
}
type MultOperation struct {
	num1 int
	num2 int
}
type Context struct {
	strategy Strategy
}
type OperationFactory struct {
	operation string
}

func (opf OperationFactory) MakeNewContext(num1, num2 int) Strategy {
	switch opf.operation {
	case "+":
		return AddOperation{
			num1,
			num2,
		}
	case "-":
		return SubOperation{
			num1,
			num2,
		}
	case "*":
		return MultOperation{
			num1,
			num2,
		}
	default:
		return nil
	}
}
func (addOperation AddOperation) DoOperation() int {
	fmt.Print(addOperation.num1, "+", addOperation.num2, "=")
	return addOperation.num1 + addOperation.num2
}
func (subOperation SubOperation) DoOperation() int {
	fmt.Print(subOperation.num1, "-", subOperation.num2, "=")
	return subOperation.num1 + subOperation.num2
}
func (multOperation MultOperation) DoOperation() int {
	fmt.Print(multOperation.num1, "*", multOperation.num2, "=")
	return multOperation.num1 + multOperation.num2
}
func (ct Context) Excute() int {
	return ct.strategy.DoOperation()
}

func main() {
	context := Context{
		OperationFactory{
			"+",
		}.MakeNewContext(1, 2),
	}
	fmt.Println(context.Excute())
}

Template method pattern

Responsibility chain model

Command mode

Interpreter mode

Iterative mode

Intermediary model

Memo mode

State mode

Visitor mode

3. Structural mode

Adapter mode

Intent: convert the interface of a class into another interface that the customer wants. The adapter pattern allows classes that cannot work together because of interface incompatibility to work together.

uml design:

package main

import (
	"fmt"
)

type CommonPowerStrip interface { //Conventional socket interface
	Use()
}
type MidThree struct { //Middle three pin plug

}
type SmallThree struct { //Small three pin plug

}
type SpecialPowerStrip interface { //Special socket
	Insert()
}
type LargeThree struct { //Large three pin socket

}
type AdapterPowerStrip struct { //Adapter
	largeThree LargeThree
}

func NewAdapterPowerStrip() AdapterPowerStrip {
	return AdapterPowerStrip{
		largeThree: LargeThree{},
	}
}
func (adapterPowerStrip AdapterPowerStrip) Use() {
	adapterPowerStrip.largeThree.Insert()
	fmt.Println("Adapted......")
	fmt.Println("The large triangular plug can be inserted into the socket of the conventional socket")
}
func (largeThree LargeThree) Insert() {
	fmt.Println("The large triangular plug can be inserted into a special socket")
}
func (mt MidThree) Use() {
	fmt.Println("The middle triangle plug can be inserted into the conventional socket interface")
}
func (st SmallThree) Use() {
	fmt.Println("The small triangular plug can be inserted into the conventional socket interface")
}
func main() {
	st := SmallThree{}
	st.Use()
	largeThree := LargeThree{}
	//Adapt
	adapterPowerStrip := AdapterPowerStrip{
		largeThree,
	}
	adapterPowerStrip.Use()
}

result:

The small triangular plug can be inserted into the conventional socket interface
 The large triangular plug can be inserted into a special socket
 Adapted
 The large triangular plug can be inserted into the socket of the conventional socket

Bridging mode

Separate the abstract part from the implementation part so that they can change independently.

Decorator mode

Dynamically add some additional responsibilities to an object. In terms of adding functionality, the decorator pattern is more flexible than generating subclasses.

uml:

package main

import "fmt"

type FastFood interface { //Fast food (abstract component role)
	SetDesc(desc string)
	SetPrice(price int)
	GetDesc() string
	GetPrice() int
}
type AbstractFastFood struct { //Implement FastFood interface
	Desc  string
	Price int
}

//Specific components
type FastRice struct { //Fried rice inherits AbstractFastFood
	aff AbstractFastFood
}

//Specific components
type FastNoddles struct { //Fried noodles inherit AbstractFastFood
	aff AbstractFastFood
}

func (aff AbstractFastFood) SetDesc(desc string) {
	aff.Desc = desc
}
func (aff AbstractFastFood) GetDesc() string {
	return aff.Desc
}
func (aff AbstractFastFood) SetPrice(price int) {
	aff.Price = price
}
func (aff AbstractFastFood) GetPrice() int {
	return aff.Price
}

//Abstract decoration
type Garmish struct {
	aff AbstractFastFood
}

func (gm Garmish) SetDesc(desc string) {
	gm.aff.SetDesc(desc)
}
func (gm Garmish) GetDesc() string {
	return gm.aff.GetDesc()
}
func (gm Garmish) SetPrice(price int) {
	gm.aff.SetPrice(price)
}
func (gm Garmish) GetPrice() int {
	return gm.aff.GetPrice()
}
func (gm Garmish) SetAbstractFastFood(ff FastFood) {

}
func (gm Garmish) GetAbstractFastFood() FastFood {
	return gm.aff
}
func NewFastRice() FastRice {
	return FastRice{
		AbstractFastFood{
			"Fried rice",
			10,
		},
	}
}
func NewFastNoddles() FastRice {
	return FastRice{
		AbstractFastFood{
			"Stir-Fried Noodles with Vegetables",
			15,
		},
	}
}

//Specific decoration
type Egg struct { //Inherit Garmish
	gm    Garmish
	desc  string
	price int
}

//Specific decoration
type Hotleg struct { //Inherit Garmish
	gm    Garmish
	desc  string
	price int
}

func NewHotleg(gm Garmish) Hotleg {
	return Hotleg{
		gm:    gm,
		desc:  "Ham",
		price: gm.GetPrice() + 2,
	}
}
func NewEgg(gm Garmish) Egg {
	return Egg{
		gm:    gm,
		desc:  "egg",
		price: gm.GetPrice() + 5,
	}
}
func (hl Hotleg) GetHotlegdesc() string {
	return hl.gm.GetDesc() + hl.desc
}
func (egg Egg) GetEggdesc() string {
	return egg.gm.GetDesc() + egg.desc
}
func (egg Egg) GetEggprice() int {
	return egg.price
}
func (hl Hotleg) GetHotlegprice() int {
	return hl.price
}
func main() {
	noddles := NewFastNoddles().aff //I'd like fried noodles
	gm := Garmish{noddles}
	egg := NewEgg(gm)
	fmt.Printf("egg.gm.GetDesc(): %v\n", egg.GetEggdesc())
	fmt.Printf("egg.gm.GetPrice(): %v\n", egg.GetEggprice())
	hl := NewHotleg(gm)
	fmt.Printf("hl.GetHotlegdesc(): %v\n", hl.GetHotlegdesc())
	fmt.Printf("hl.GetHotlegprice(): %v\n", hl.GetHotlegprice())
}

output

egg.gm.GetDesc(): Fried noodles and eggs
egg.gm.GetPrice(): 20
hl.GetHotlegdesc(): Fried noodles and ham
hl.GetHotlegprice(): 17

Appearance mode

Make the customer and the system decouple, use the interface exposed by the appearance class, and the appearance class combines and arranges the methods of the system

package main

import "fmt"

type Cilent struct {
	fa Facade
}
type Facade struct {
	s1 System1
	s2 System2
	s3 System3
	s4 System4
}
type System1 struct {
}
type System2 struct {
}
type System3 struct {
}
type System4 struct {
}

func (f Facade) MenthonA() {
	fmt.Println("appearance A Method group")
	f.s1.Menthod1()
	f.s2.Menthod2()
}
func (f Facade) MenthonB() {
	fmt.Println("appearance B Method group")
	f.s2.Menthod2()
	f.s3.Menthod3()
}
func (s System1) Menthod1() {
	fmt.Println("This is method 1")
}
func (s System2) Menthod2() {
	fmt.Println("This is method 2")
}
func (s System3) Menthod3() {
	fmt.Println("This is method 3")
}
func (s System4) Menthod4() {
	fmt.Println("This is method 4")
}
func newFacade() Facade {
	return Facade{
		s1: System1{},
		s2: System2{},
		s3: System3{},
		s4: System4{},
	}
}
func (c Cilent) Use() {
	c.fa = newFacade()
	c.fa.MenthonA()
}
func main() {
	c := Cilent{}
	c.Use()

}

proxy pattern

Combination mode

Sharing element mode

some suggestions:

Programming by interface; Try to use composition instead of inheritance; Find out the reason for the change.

Topics: Go Back-end