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.