
设计模式(design pattern):是对软件设计中普遍存在、反复出现的问题所提出的解决方案,这里的问题就是我们应该怎么去写/设计我们的代码,让我们的代码可读性、可扩展性、可重用性、可靠性更好,通过合理的代码设计让我们的程序拥有“高内聚,低耦合”的特性,这就是设计模式要解决的问题。
本质是为了提高软件的可维护性、可扩展性、通用性,并降低软件的复杂度。
1. 创建型模式:提供创建对象的机制,增加已有代码的灵活性和可复用性。
2. 结构型模式:介绍如何将对象和类组装成较大的结构,并同时保持结构的灵活和高效
3. 行为型模式:负责对象间的高效沟通和职责委派
设计模式是前人摸索出来的一种代码设计经验,学习它就像站在巨人的肩膀上,参悟其中的设计理念,从而在实践中写出高质量代码。因此不论是编程新手还是老鸟,都应该去学习设计模式。
1. 代码复用
2. 扩展性
设计原则
原则:面向接口进行开发,而不是面向实现;依赖于抽象类型,而不是具体类型。
简单工厂就是我们首先声明一个类,这个类叫做工厂类,在这个内我们可以声明一个(静态)方法,这个方法会根据参数的值生成相应的对象(我们把这个对象叫“产品”)。
简单工厂的好处是:
代码
- package main
-
- import "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 main
-
- import "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 main
-
- import "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 main
-
- import (
- "fmt"
- "sync"
- )
-
- // 数据库连接对象实例
- type DBInstance struct {
- }
-
- var (
- once sync.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 main
-
- import "fmt"
-
- type Car struct {
- engine string
- chassis string
- body string
- }
-
- // 生成器
- type CarBuilder struct {
- engine string
- chassis string
- body string
- }
-
- 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 main
-
- import "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 main
-
- import "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
如果在增加新的需求维度,那么类的数量会出现质数增长,这是我们不能接受的
解决方案
其实我们之前是在用继承的想法来看问题,桥接模式则希望将继承关系转变为关联关系,使两个类独立存在,且一个类通过实现接口聚合在另一个类中。
详细说一下:
用一句话总结桥接模式的理念就是:“将抽象与实现接口,将不同的类别的继承关系改为关联关系”
代码
- package main
-
- import "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 main
-
- import "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 main
-
- import "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 main
-
- import "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 main
-
- import "fmt"
-
- // 策略维护对象,包含了路线信息name 和 所用的的策略strategy
- type Travel struct {
- name string
- 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 流程都是相同的:
后续引入的任何新 OTP 类型都很有可能需要进行相同的上述步骤。
因此, 我们会有这样的一个场景, 其中某个特定操作的步骤是相同的, 但实现方式却可能有所不同。 这正是适合考虑使用模板方法模式的情况。
首先, 我们定义一个由固定数量的方法组成的基础模板算法。 这就是我们的模板方法。 然后我们将实现每一个步骤方法, 但不会改变模板方法。
代码
- package main
-
- import "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)
- }
文章参考: