设计模式|GO 常用设计模式

设计模式|GO 常用设计模式
文章图片


设计模式简介 什么是设计模式 设计模式(design pattern):是对软件设计中普遍存在、反复出现的问题所提出的解决方案,这里的问题就是我们应该怎么去写/设计我们的代码,让我们的代码可读性、可扩展性、可重用性、可靠性更好,通过合理的代码设计让我们的程序拥有“高内聚,低耦合”的特性,这就是设计模式要解决的问题。
本质是为了提高软件的可维护性、可扩展性、通用性,并降低软件的复杂度。
设计模式分类 1. 创建型模式:提供创建对象的机制,增加已有代码的灵活性和可复用性。
2. 结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效
3. 行为型模式:负责对象间的高效沟通和职责委派
我们为什么要学 设计模式是前人摸索出来的一种代码设计经验,学习它就像站在巨人的肩膀上,参悟其中的设计理念,从而在实践中写出高质量代码。因此不论是编程新手还是老鸟,都应该去学习设计模式。
软件设计原则 1. 代码复用
2. 扩展性

设计原则
封装变化的内容

面向接口开发,而不是面向实现
原则:面向接口进行开发,而不是面向实现;依赖于抽象类型,而不是具体类型。

组合优于继承

SOLID原则
一. 单一职责原则(Single Responsibility Principle)
二. 开闭原则(Open/Closed Principle)
三. 里氏替换原则(Liskov Substitution Principle)
四. 接口隔离原则(Interface Segregation Principle)
五. 依赖倒置原则(Dependency Inversion Principle)

创建模式 简单工厂 简单工厂就是我们首先声明一个类,这个类叫做工厂类,在这个内我们可以声明一个(静态)方法,这个方法会根据参数的值生成相应的对象(我们把这个对象叫“产品”)。
简单工厂的好处是:

  1. 使用者可以直接获得一个构造好的对象(根据我们在工厂类内方法的传参值),而不需要关心这个对象的构造的过程。
  2. 当我们新增一个对象(产品)时,我们只需要修改这个工厂类内的方法就行了,这样降低了所需对象(产品)与我们使用对象的代码逻辑的耦合,符合开闭原则。
代码
package mainimport "fmt"// 对象接口 type BMW interface { run() }// 构建对象1 type BMW730 struct { } func (b BMW730) run() { fmt.Println("BMW730 is running...") }// 构建对象2 type BMW840 struct { } func (b BMW840) run() { fmt.Println("BMW840 is running...") }// 工厂对象 type Factory struct { } func (f Factory)produceBMW(BMW_TYPE string) BMW { switch BMW_TYPE { case "BMW730": return BMW730{} case "BMW840": return BMW840{} default: return nil } }func main() { // 生成工厂对象 factory := new(Factory) // 使用工厂对象生成产品对象 p1 := factory.produceBMW("BMW730") p1.run() p2 := factory.produceBMW("BMW840") p2.run() }

工厂方法 在工厂方法模式(Factory Methord Pattern)中,工厂父类(在go中为interface)负责定义创建产品对象的公共接口,子工厂类要实现父工厂中定义的接口,每一个工厂子类则负责生成具体种类的产品对象,这样做的目的是将产品类的实例化操作延迟到工厂子类中完成。
工厂方法的好处:
在工厂方法中,客户端不需要知道产品的类名,只需要知道所对应的子工厂类即可,具体的产品对象由具体的子工厂类创建,客户端只需要知道创建具体产品的子工厂类对象就行。
当要有新种类的产品对象出现时,我们只需要要新增生产这个产品的子工厂类(这个类去实现父工厂类中定义的接口),就可以通过这个子工厂类就能生产这个新产品对象了,此时我们没有对代码进行修改,只是对代码进行了扩展(新增了一个子工厂类),因此符合开闭原则。
代码
package mainimport "fmt"// 对象接口 type BMW interface { run() }// 构建对象1 type BMW730 struct { } func (b BMW730) run() { fmt.Println("BMW730 is running...") }// 构建对象2 type BMW840 struct { } func (b BMW840) run() { fmt.Println("BMW840 is running...") }// 抽象父类工厂 type Factory interface { produceBMW() BMW } // 子类实现父抽象类接口,生成特定种类的产品BMW730 type BMW730Factory struct { } func (b BMW730Factory) produceBMW() BMW { return BMW730{} }// 子类实现父抽象类接口,生成特定种类的产品BMW840 type BMW840Factory struct { } func (b BMW840Factory) produceBMW() BMW { return BMW840{} }// 通过向参数中传不同的子类,生成不同的产品(某种车) func get_bmw(bmw Factory) { car := bmw.produceBMW() car.run() }func main() { // 生成不同子工厂对象 factory_730 := new(BMW730Factory) factory_840 := new(BMW840Factory) // 通过不同子工厂对象,生产不同种类的车 get_bmw(factory_730) get_bmw(factory_840) }


抽象工厂 抽象工厂(Abstract Factory Pattern)通过提供了一个抽象工厂类,这个抽象工厂类定义了多个接口,每个接口都可以生产一种产品。子类工厂在实现抽象工厂的接口后,可以通过不同的接口生产出不同种类的对象。
代码
package mainimport "fmt"// 构建对象1 type BMW730 struct { } func (b BMW730) run() { fmt.Println("BMW730 is running...") }// 构建对象2 type BMW840 struct { } func (b BMW840) run() { fmt.Println("BMW840 is running...") }// 抽象工厂 type BMWFactory interface { produce730BMW() BMW730 produce840BMW() BMW840 }// 具体工厂子类 type ConcreteBMWFactory struct { }func (c ConcreteBMWFactory) produce730BMW() BMW730 { return BMW730{} } func (c ConcreteBMWFactory) produce840BMW() BMW840 { return BMW840{} } func main() { // 实例化子类工厂,这个工厂可以根据不同的函数生成不同种类的车产品 concreteBMWFactory := ConcreteBMWFactory{} bmw730 := concreteBMWFactory.produce730BMW() bmw840 := concreteBMWFactory.produce840BMW() bmw730.run() bmw840.run() }

单例模式 单例模式(Singleton Pattern)是一种简单的创建模式,有些对象我们往往只需要全局一个,比如全局缓存、数据库连接等。
使用场景
需要频繁实例化然后销毁的对象
创建对象耗时过多或资源过多,但有经常用到
系统只需要一个实例对象,如果系统要求提供一个唯一的序列号生成器或资源管理器,或者对象消耗资源太大而只允许创建一个对象。

代码
package mainimport ( "fmt" "sync" )// 数据库连接对象实例 type DBInstance struct { }var ( oncesync.Once dbInstance *DBInstance )// 注意初始对象实例过程也可以放到init函数中进行 func NewInstance() *DBInstance { // 只有第一次才会实例化数据库连接对象 if dbInstance != nil { once.Do(func() { dbInstance = &DBInstance{} }) } return dbInstance }func main() { for i := 0; i <= 10; i++ { // 在使用的时候获取数据库实例对象 db := NewInstance() fmt.Println(db) } }

建造者模式 建造者模式(Build Pattern)又叫生成器,将一个复杂对象分解成多个相对简单的部分,之后按步骤创建这个复杂对象的每一个部分。该模式允许你使用相同的创建代码生成不同的对象。

代码
package mainimport "fmt"type Car struct { enginestring chassis string bodystring }// 生成器 type CarBuilder struct { enginestring chassis string bodystring }func (c *CarBuilder) addChassis(chassis string) { c.chassis = chassis }func (c *CarBuilder) addEngine(engine string) { c.engine = engine }func (c *CarBuilder) addBody(body string) { c.body = body }func (c *CarBuilder) build() Car { return Car{ engine: c.engine, chassis: c.chassis, body:c.body, } }func main() { carBuilder := &CarBuilder{} // 按步骤创建对象 carBuilder.addEngine("v12") carBuilder.addChassis("复合材料") carBuilder.addBody("镁合金") // 构建Car对象 car := carBuilder.build() fmt.Println(car) }

原型模式 如果你希望生成一个对象,这个对象与另一个对象完全相同,该如何实现呢?
如果遍历对象的所有成员,将其依次复制到新对象中,会稍显麻烦。
原型模式(Prototype Pattern)将这个克隆新对象过程委派给了被克隆对象(其实就是将这个被克隆对象的类中增加一个克隆函数),被克隆对象叫做原型。
注意:在写克隆函数时,要注意深浅拷贝问题。
package mainimport "fmt"type obj struct { name *string }func (o *obj) Clone() *obj { new_obj := *o return &new_obj }func main() { name := "qiliang" o1 := &obj{name: &name} o2 := o1.Clone() // 注意会有浅拷贝问题 *o2.name = "xiaolin" fmt.Println(*o1.name) fmt.Println(*o2.name) }

结构型模式 适配器模式 适配器模式(Adaptor Pattern)说白了就是兼容,假设一开始我们提供了A对象,后期随着业务迭代,有需要在A对象的基础上衍生出不同的需求。如果有很多函数已经在线上调用了A对象,此时再对A对象修改就比较麻烦,也可能会出现问题。甚至更糟糕的情况,你坑你没有程序库的源代码,从而无法进行修改。
此时就可以用一个适配器,它就像一个接口转换器,调用方只需要调用这个适配器,而不需要去关心背后的实现,由适配器接口封装复杂的逻辑转换过程。
package mainimport "fmt"type CM2M struct { }func (c CM2M) CMtoM(cm float64) (m float64) { m = cm / 100 return }type M2CM struct { }func (c M2CM) MtoCM(m float64) (cm float64) { cm = m * 100; return cm }// 适配器函数,自动进行cm与m之间的转换 // 有了这个适配器逻辑后我们,不需要再关注转换的逻辑 type AdapterTransLength interface { transLength(string, float64) float64 }type TransLengthAdapter struct { }func (t TransLengthAdapter)transLength(whickType string, length float64) float64 { if whickType == "m" { return M2CM{}.MtoCM(length) } return CM2M{}.CMtoM(length) }func main(){ transAdapter := TransLengthAdapter{} ans := transAdapter.transLength("m", 10) fmt.Println(ans) }

桥接模式 假设一开始业务需要两种发送消息渠道:sms和email,我们可以分别实现sms和email接口。
之后随着业务迭代,又产生了新的需求,需要提供两种系统发送的方式,systemA和systemB并且这两种系统发送方式都应该支持sms和email渠道
此时至少需要提供4中方法:systemA to sms、systemA to email、systemB to sms、systemB to email
如果在增加新的需求维度,那么类的数量会出现质数增长,这是我们不能接受的
解决方案
其实我们之前是在用继承的想法来看问题,桥接模式则希望将继承关系转变为关联关系,使两个类独立存在,且一个类通过实现接口聚合在另一个类中。
详细说一下:
  1. 桥接模式需要将抽象和实现区分开;
  2. 桥接模式需要将“渠道”和“学习通发送方式”这两种类别区分开;
  3. 最后再“系统发送方式”的雷利调用“渠道”的抽象接口,使他们从继承关系转变为关联关系。
用一句话总结桥接模式的理念就是:“将抽象与实现接口,将不同的类别的继承关系改为关联关系”
代码
package mainimport "fmt"// 声明消息接口 type SendMessage interface { send(text, to string) }// 声明第一种消息 type sms struct { }func (s sms) send(text, to string) { fmt.Println(fmt.Sprintf("send %s to %s sms", text, to)) }func NewSms() *sms { return &sms{} }// 声明第二种消息 type email struct { }func (e email) send(text, to string) { fmt.Println(fmt.Sprintf("send %s to %s email", text, to)) }func NewEmail() *email { return &email{} }// 声明两种发送渠道,这两种发送渠道都要支持sms和email消息(通过组合SendMessage接口来实现 // 本质是因为在组合SendMessage接口后,sms、emali都可以放到systemA中的method字段,当method字段值不同时就可以发送不同的消息) type systemA struct { method SendMessage }func NewSystemA(method SendMessage) *systemA { return &systemA{ method: method, } }func (s *systemA) SystemASendMes(text, to string) { s.method.send("SystemA "+text, to) }type systemB struct { method SendMessage }func NewSystemB(method SendMessage) *systemB { return &systemB{ method: method, } }func (b *systemB) SystemBSendMes(text, to string) { b.method.send("SystemB " + text, to) }func main() { // 声明要发送的类型 sms := NewSms() email := NewEmail() // 在SystemA中发送两种消息 systemA1 := NewSystemA(sms) systemA2 := NewSystemA(email) systemA1.SystemASendMes("ni hao !", "qiliang") systemA2.SystemASendMes("ni hao !", "qiliang") systemB1 := NewSystemB(sms) systemB2 := NewSystemB(email) systemB1.SystemBSendMes("gan xie !", "xiaoming") systemB2.SystemBSendMes("gan xie !", "xiaoming") }

装饰器模式 有时候我们对一个类进行封装,形成一个新类,这个新类在原类的基础上拥有额外的功能。
例如一个披萨类,你可以在对披萨类进行封装,形成新的类如番茄披萨类和芝士披萨类。此时这个封装就是装饰器模式的核心思想。
简单来说,装饰器模式就是将对象封装到形成一个新对象,这个信息对象功能更丰富(可以理解为:为源对象绑定了新的行为功能)
如果你希望在无需修改对象的情况下使用对象,并且希望对象新增额外的行为,就可以考虑使用装饰器模式。
代码
package mainimport "fmt"type pizza interface { getPrice() int }type basePizza struct { }func (p basePizza) getPrice() int { return 15 }type tomatoPizza struct { pizza pizza }func (p tomatoPizza) getPrice() int { return p.pizza.getPrice() + 10 }type cheesePizza struct { pizza pizza }func (p cheesePizza) getPrice() int { return p.pizza.getPrice() + 20 }func main() { tomPizza := tomatoPizza{} tomPizza.pizza = basePizza{} price := tomPizza.getPrice() fmt.Println(price) }

代理模式 如果你需要在访问一个对象时,有一个像“代理”一样的角色,它可以在访问对象之前为你进行缓存检查、权限判断等访问控制,在访问对象之后为你进行结果缓存、日志记录等处理,那么可以考试率使用代码模式(Proxy Pattern)。
会议一下一些web框架的router模块,当客户端访问一个接口时,在最终执行对应的接口之前,router模块会执行一些事前操作,进行权限判断操作,在执行之后会记录日志,这就是典型的代理模式。
代理模式需要一个代理类,其包含执行真正对象所需要的成员变量,并由代理类管理整个声明周期。
代码
package mainimport "fmt"type Subject interface { ProxyFun() string }// 声明代理类 type Proxy struct { real RealSubject }func (p Proxy) ProxyFun() string { var ans string // 在低啊用真实对象之前,检查缓存、判断权限等 p.real.PreFun() p.real.RealFun() p.real.AfterFun() // 在调用完操作之后,可以缓存结果、对结果进行处理等(如脱敏)、记录日志等 return ans }type RealSubject struct { }func (s RealSubject) RealFun() { fmt.Println("real...") }func (s RealSubject) PreFun() { fmt.Println("Pre...") }func (s RealSubject) AfterFun() { fmt.Println("After...") }func main() { rs := RealSubject{} proxy := Proxy{real: rs} proxy.ProxyFun() }


行为行模式 观察者模式 如果你需要在对一个对象的状态被改变时,其他对象能作为“观察者”被通知,就可以使用观察者模式。
我们将自身状态改变就回通知其他对象的对象称为“发布者”,关注发布者状态变化的对象称为“订阅者”。

代码
package mainimport "fmt"// 发布者-主题 type Subject struct { observers []Observer content string }func NewSubject() *Subject { return &Subject{ observers: make([]Observer, 0), } }// 添加订阅者 func (s *Subject) AddObserver(o Observer) { s.observers = append(s.observers, o) }// 通知消费者 func (s *Subject) Notify() { for _, o := range s.observers { o.SendMessage(s) } }// 发布消息 func (s *Subject) UpdateContent(content string) { s.content = content s.Notify() }// 观察者-订阅者接口 type Observer interface { SendMessage(*Subject) }// 订阅者 type Reader struct { name string }func NewReader(name string) *Reader { return &Reader{ name: name, } }func (r Reader) SendMessage(s *Subject) { fmt.Println(r.name + " " + s.content) }func main() { subject := NewSubject() reader1 := NewReader("qiliang") reader2 := NewReader("xiaolin") subject.AddObserver(reader1) subject.AddObserver(reader2) subject.UpdateContent("ni hao !")}


策略模式 假设需要实现一个根据出行方式规划路线导航功能,出行的方式可以选择不行、骑行、开车,最简单的方式就是分别实现这个3种方法,供客户端调用。但是这样做会使客户端选择路线的代码和出行方式耦合了起来,如果新增一种出行的方式,就要修改客户端路线选择方案代码,这不符合开闭原则。
解决办法是使用策略模式(Strategy Pattern),它会将每种出行方案都抽取到一组新的类中,组中的类都会实现一个出行Interface,这个出行Interface约定了出行接口函数。客户端只需要指定指定所需要的出行方案即可。


代码
package mainimport "fmt"// 策略维护对象,包含了路线信息name 和 所用的的策略strategy type Travel struct { namestring strategy Strategy }func NewTravel(name string, strategy Strategy) *Travel { return &Travel{ name:name, strategy: strategy, } }// 执行路线策略 func (p *Travel) getTraffic() { p.strategy.traffic(p) }// 路线策略接口 type Strategy interface { traffic(*Travel) }// 实现走路方式 type walk struct { }func (w *walk) traffic(t *Travel) { fmt.Println(t.name + " walk...") } // 实现开车方式 type drive struct { }func (w *drive) traffic(t *Travel) { fmt.Println(t.name + " drive...") }func main() { // 生成走路路线策略 travel1 := NewTravel("qiliang", &walk{}) // 生成开车路线策略 travel2 := NewTravel("xiaolin", &drive{}) // 运行策略 travel1.getTraffic() travel2.getTraffic() }// OutPut qiliang walk... xiaolin drive...

模板方法模式模板方法建议将过程/算法分解为一系列步骤(就比如:盖房子这个过程,对盖普通的房子或者盖别墅,它的大致步骤是相同,我们可以简单考虑的将盖房子的过程分为:步骤1 打地基、步骤2 建围墙、步骤3 盖房顶 这个三个基类中的方法)
然后将这些步骤改写成基类中的方法, 当我子类去继承基类时,子类也拥有了这些方法,并且在子类中我们可以重写基类中的某些方法,让子类更适配某个具体的流程(比如这个子类表示的是 “盖别墅类”, 那么我们可以重写“盖别墅类中”的“步骤1 打地基”方法,因为盖别墅打的地基,可能和基类中默认实现的打地基的方式不太一样,通过重写这个基类中的“步骤1 打地基”方法)。
如果以后新增加 “盖小区的”这个过程,我们仍然可以使用 基类中的定义的模板方法(就是盖房子的三个步骤),并对某些方法进行重写,因为我们只重写了部分方法,其它的步骤是用基类的默认方法,这样就减少了我们的代码量工作。
举例:
让我们来考虑一个一次性密码功能 (OTP) 的例子。 将 OTP 传递给用户的方式多种多样 (短信、 邮件等)。 但无论是短信还是邮件, 整个 OTP 流程都是相同的:
  1. 生成随机的 n 位数字。
  2. 在缓存中保存这组数字以便进行后续验证。
  3. 准备内容。
  4. 发送通知。
后续引入的任何新 OTP 类型都很有可能需要进行相同的上述步骤。
因此, 我们会有这样的一个场景, 其中某个特定操作的步骤是相同的, 但实现方式却可能有所不同。 这正是适合考虑使用模板方法模式的情况。
首先, 我们定义一个由固定数量的方法组成的基础模板算法。 这就是我们的模板方法。 然后我们将实现每一个步骤方法, 但不会改变模板方法。

代码
package mainimport "fmt"// 定义OTP步骤接口 type IOtp interface { genRandomOTP(int) string saveOTPCache(string) getMessage(string) string sendNotification(string) error }// 对IOtp封装一下 type Otp struct { iOtp IOtp }// 相同OTP执行步骤调用 func (o *Otp) genAndSendOTP(otpLength int) error { otp := o.iOtp.genRandomOTP(otpLength) o.iOtp.saveOTPCache(otp) message := o.iOtp.getMessage(otp) err := o.iOtp.sendNotification(message) if err != nil { return err } return nil }// 定义Sms实现IOtp接口 type Sms struct { }func (s *Sms) genRandomOTP(len int) string { randomOTP := "1234" fmt.Printf("SMS: generating random otp %s\n", randomOTP) return randomOTP }func (s *Sms) saveOTPCache(otp string) { fmt.Printf("SMS: saving otp: %s to cache\n", otp) }func (s *Sms) getMessage(otp string) string { return "SMS OTP for login is " + otp }func (s *Sms) sendNotification(message string) error { fmt.Printf("SMS: sending sms: %s\n", message) return nil }// 定义Email实现IOtp接口 type Email struct { }func (s *Email) genRandomOTP(len int) string { randomOTP := "1234" fmt.Printf("EMAIL: generating random otp %s\n", randomOTP) return randomOTP }func (s *Email) saveOTPCache(otp string) { fmt.Printf("EMAIL: saving otp: %s to cache\n", otp) }func (s *Email) getMessage(otp string) string { return "EMAIL OTP for login is " + otp }func (s *Email) sendNotification(message string) error { fmt.Printf("EMAIL: sending email: %s\n", message) return nil }func main() { smsOTP := &Sms{} o := Otp{ iOtp: smsOTP,// 接口实现多态 } o.genAndSendOTP(4) fmt.Println("") emailOTP := &Email{} o = Otp{ iOtp: emailOTP,// 接口实现多态 } o.genAndSendOTP(4) }



文章参考:
Go 常用设计模式
图解九种常见的设计模式 - SegmentFault 思否
【设计模式|GO 常用设计模式】用Go语言实现23种设计模式 - 掘金

    推荐阅读