11.Go语言设计模式
1 设计原则
- 单一职责原则:一个方法只完成一件事,实现高内聚低耦合。
- 开闭原则:对扩展开发,对修改关闭(常见做法:多态、基于接口实现、依赖注入)。
- 里氏替换原则:子类可以扩展父类的功能,但不能改变父类原有的功能。也就是说:子类继承父类时,除添加新的方法完成新增功能外,尽量不要重写父类的方法。
- 接口隔离原则:尽量将臃肿庞大的接口拆分成更小的和更具体的接口;单一职责原则注重的是职责,而接口隔离原则注重的是对接口依赖的隔离。
- 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象;依赖倒置原则是实现开闭原则的重要途径之一,它降低了客户与实现模块之间的耦合。
- 迪米特原则:
只与你的直接朋友交谈,不跟“陌生人”说话
,从依赖者的角度来说,只依赖应该依赖的对象。从被依赖者的角度说,只暴露应该暴露的方法。
2 设计模式
-
大类:
- 创建型模式: 它提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。
- 结构性模式
- 行为型模式
序号 模式 & 描述 包括 1 创建型模式
这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。这使得程序在判断针对某个给定实例需要创建哪些对象时更加灵活。**工厂模式(**Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)2 结构型模式
这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。适配器模式(Adapter Pattern)
桥接模式(Bridge Pattern)
过滤器模式(Filter、Criteria Pattern)
组合模式(Composite Pattern)
装饰器模式(Decorator Pattern)
外观模式(Facade Pattern)
享元模式(Flyweight Pattern)
代理模式(Proxy Pattern)3 行为型模式
这些设计模式特别关注对象之间的通信。责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)观察者模式(Observer Pattern)状态模式(State Pattern)空对象模式(Null Object Pattern)策略模式(Strategy Pattern)模板模式(Template Pattern)访问者模式(Visitor Pattern) -
举例
- 单例模式 once.Do
- 工厂模式 : 自定义一个结构体构造方法
- options模式: 把函数生成可选参数
- 代理模式: 字符串占位符
- 生产者消费者模式: 单向channel
- 建造者模式: Gorm 大量用了建造者模式, 例如创建一个new方法,生成某个结构体实例
2.1 1. 单例模式(创建型)
单例模式(Singleton Pattern),是最简单的一个模式。在Go中,单例模式指的是全局只有一个实例,并且它负责创建自己的对象。单例模式不仅有利于减少内存开支,还有减少系统性能开销、防止多个实例产生冲突等优点。
因为单例模式保证了实例的全局唯一性,而且只被初始化一次,所以比较适合全局共享一个实例,且只需要被初始化一次的场景,例如数据库实例、全局配置、全局任务池等。
2.1.1 1.1 饿汉模式
懒汉方式指全局的单例实例在第一次被使用时创建。因为实例是在包被导入时初始化的,所以如果初始化耗时,会导致程序加载时间比较长。
-
实现方式
1 2 3 4 5 6 7 8 9 10
package singleton type singleton struct { } var ins *singleton = &singleton{} func GetInsOr() *singleton { return ins }
-
饿汉模式可以将问题及早暴露,懒汉式虽然支持延迟加载,但是这只是把冷启动时间放到了第一次使用的时候,并没有本质上解决问题,并且为了实现懒汉式还不可避免的需要加锁
2.1.2 1.2 懒汉模式
懒汉方式真正使用的时候才会创建实例,运用较广,但它的缺点是非并发安全,在实际使用时需要加锁。
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
package singleton type singleton struct { } var ins *singleton func GetInsOr() *singleton { if ins == nil { ins = &singleton{} } return ins }
-
非并发安全,在实际使用时需要加锁。
2.1.3 1.3 双重锁定
双重锁检查是在懒汉模式的基础上加锁实现,保证并发安全
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package singleton import "sync" type singleton struct { } var ins *singleton var mu sync.Mutex func GetIns() *singleton { if ins == nil { mu.Lock() defer mu.Unlock() ins = &singleton{} } return ins }
-
sync.Once 就是基于双重锁定的封装
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package singleton import ( "sync" ) type singleton struct { } var ins *singleton var once sync.Once func GetInsOr() *singleton { once.Do(func() { ins = &singleton{} }) return ins }
-
实现原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
package sync import ( "sync/atomic" ) type Once struct { done uint32 m Mutex } func (o *Once) Do(f func()) { if atomic.LoadUint32(&o.done) == 0 { // Outlined slow-path to allow inlining of the fast-path. o.doSlow(f) } } func (o *Once) doSlow(f func()) { o.m.Lock() defer o.m.Unlock() if o.done == 0 { defer atomic.StoreUint32(&o.done, 1) f() } }
-
2.2 2. 工厂模式(创建型)
工厂模式(Factory Pattern)是面向对象编程中的常用模式。在Go项目开发中,你可以通过使用多种不同的工厂模式,来使代码更简洁明了。Go中的结构体,可以理解为面向对象编程中的类,例如 Person结构体(类)实现了Greet方法。
-
结构体定义
1 2 3 4 5 6 7 8
type Person struct { Name string Age int } func (p Person) Greet() { fmt.Printf("Hi! My name is %s", p.Name) }
2.2.1 2.1 简单工厂模式
-
简单工厂模式是最常用、最简单的。它就是一个接受一些参数,然后返回Person实例的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
type Person struct { Name string Age int } func (p Person) Greet() { fmt.Printf("Hi! My name is %s", p.Name) } func NewPerson(name string, age int) *Person { return Person{ Name: name, Age: age } }
2.2.2 2.2 抽象工厂模式
-
和简单工厂模式的唯一区别,就是它返回的是接口而不是结构体。通过返回接口,可以在你不公开内部实现的情况下,让调用者使用你提供的各种功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
type Person interface { Greet() } type person struct { name string age int } func (p person) Greet() { fmt.Printf("Hi! My name is %s", p.name) } // Here, NewPerson returns an interface, and not the person struct itself func NewPerson(name string, age int) Person { return person{ name: name, age: age } }
2.2.3 2.3 工厂方法模式
-
在简单工厂模式中,依赖于唯一的工厂对象,如果我们需要实例化一个产品,就要向工厂中传入一个参数,获取对应对象;如果要增加一种产品,就要在工厂中修改创建产品的函数。这会导致耦合性过高,这时我们就可以使用工厂方法模式。
-
在工厂方法模式中,依赖工厂接口,我们可以通过实现工厂接口来创建多种工厂,将对象创建从由一个对象负责所有具体类的实例化,变成由一群子类来负责对具体类的实例化,从而将过程解耦。
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
type Person struct { name string age int } func NewPersonFactory(age int) func(name string) Person { return func(name string) Person { return Person{ name: name, age: age, } } } newBaby := NewPersonFactory(1) baby := newBaby("john") newTeenager := NewPersonFactory(16) teen := newTeenager("jill")
2.3 3. 建造者模式(创建型)
- Gorm 大量用了建造者模式, 例如创建一个new方法,生成某个结构体实例
2.4 4. 策略模式(行为型)
-
策略模式(Strategy Pattern)定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。
-
在什么时候,我们需要用到策略模式呢?
- 在项目开发中,我们经常要根据不同的场景,采取不同的措施,也就是不同的策略。比如,假设我们需要对a、b 这两个整数进行计算,根据条件的不同,需要执行不同的计算方式。我们可以把所有的操作都封装在同一个函数中,然后通过
if ... else ...
的形式来调用不同的计算方式,这种方式称之为硬编码。 - 在实际应用中,随着功能和体验的不断增长,我们需要经常添加/修改策略,这样就需要不断修改已有代码,不仅会让这个函数越来越难维护,还可能因为修改带来一些bug。所以为了解耦,需要使用策略模式,定义一些独立的类来封装不同的算法,每一个类封装一个具体的算法(即策略)。
- 在项目开发中,我们经常要根据不同的场景,采取不同的措施,也就是不同的策略。比如,假设我们需要对a、b 这两个整数进行计算,根据条件的不同,需要执行不同的计算方式。我们可以把所有的操作都封装在同一个函数中,然后通过
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
package strategy // 策略模式 // 定义一个策略类 type IStrategy interface { do(int, int) int } // 策略实现:加 type add struct{} func (*add) do(a, b int) int { return a + b } // 策略实现:减 type reduce struct{} func (*reduce) do(a, b int) int { return a - b } // 具体策略的执行者 type Operator struct { strategy IStrategy } // 设置策略 func (operator *Operator) setStrategy(strategy IStrategy) { operator.strategy = strategy } // 调用策略中的方法 func (operator *Operator) calculate(a, b int) int { return operator.strategy.do(a, b) } //// 在上述代码中,我们定义了策略接口 IStrategy,还定义了 add 和 reduce 两种策略。最后定义了一个策略执行者,可以设置不同的策略,并执行; 我们可以随意更换策略,而不影响Operator的所有实现。 func TestStrategy(t *testing.T) { operator := Operator{} operator.setStrategy(&add{}) result := operator.calculate(1, 2) fmt.Println("add:", result) operator.setStrategy(&reduce{}) result = operator.calculate(2, 1) fmt.Println("reduce:", result) }
2.5 5. 观察者模式(行为型)
-
观察者模式 (Observer Pattern),定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知,依赖对象在收到通知后,可自行调用自身的处理程序,实现想要干的事情,比如更新自己的状态。
-
观察者模式也经常被叫做发布 - 订阅(Publish/Subscribe)模式、上面说的定义对象间的一种一对多依赖关系,一 - 指的是发布变更的主体对象,多 - 指的是订阅变更通知的订阅者对象。
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
package main import "fmt" // Subject 接口,它相当于是发布者的定义 type Subject interface { Subscribe(observer Observer) // 添加订阅者 Notify(msg string) // 发布通知 } // Observer 观察者接口 相当于订阅者 type Observer interface { Update(msg string) } // Subject 实现 type SubjectImpl struct { observers []Observer } // Subscribe 添加观察者(订阅者) func (sub *SubjectImpl) Subscribe(observer Observer) { sub.observers = append(sub.observers, observer) } // Notify 发布通知 func (sub *SubjectImpl) Notify(msg string) { for _, o := range sub.observers { o.Update(msg) } } // Observer1 Observer1 type Observer1 struct{} // Update 实现观察者接口 func (Observer1) Update(msg string) { fmt.Printf("Observer1: %s\n", msg) } // Observer2 Observer2 type Observer2 struct{} // Update 实现观察者接口 func (Observer2) Update(msg string) { fmt.Printf("Observer2: %s\n", msg) } func main(){ sub := &SubjectImpl{} sub.Subscribe(&Observer1{}) sub.Subscribe(&Observer2{}) sub.Notify("Hello") }
2.6 6. 代理模式(结构型)
字符串占位符
2.7 7. 选项模式(行为型)
-
在Python语言中,创建一个对象时,可以给参数设置默认值,这样在不传入任何参数时,可以返回携带默认值的对象,并在需要时修改对象的属性。这种特性可以大大简化开发者创建一个对象的成本,尤其是在对象拥有众多属性时。
-
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
package options import ( "time" ) type Connection struct { addr string cache bool timeout time.Duration } const ( defaultTimeout = 10 defaultCaching = false ) type options struct { timeout time.Duration caching bool } // Option overrides behavior of Connect. type Option interface { apply(*options) } type optionFunc func(*options) func (f optionFunc) apply(o *options) { f(o) } func WithTimeout(t time.Duration) Option { return optionFunc(func(o *options) { o.timeout = t }) } func WithCaching(cache bool) Option { return optionFunc(func(o *options) { o.caching = cache }) } // Connect creates a connection. func Connect(addr string, opts ...Option) (*Connection, error) { options := options{ timeout: defaultTimeout, caching: defaultCaching, } for _, o := range opts { o.apply(&options) } return &Connection{ addr: addr, cache: options.caching, timeout: options.timeout, }, nil }
-
另外一种实现默认参数的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
package options import ( "time" ) const ( defaultTimeout = 10 defaultCaching = false ) type Connection struct { addr string cache bool timeout time.Duration } // NewConnect creates a connection. func NewConnect(addr string) (*Connection, error) { return &Connection{ addr: addr, cache: defaultCaching, timeout: defaultTimeout, }, nil } // NewConnectWithOptions creates a connection with options. func NewConnectWithOptions(addr string, cache bool, timeout time.Duration) (*Connection, error) { return &Connection{ addr: addr, cache: cache, timeout: timeout, }, nil }
2.8 8. 生产者消费者模式(行为型)
- 单向channel 是经典的生产者消费者模型

