严格来说 Go 并不特别适用 Java 的设计模式。但是我们主要目的是掌握方法以降低耦合、提高复用性。

单例模式

一句话总结:利用包域变量保存实例,利用 once.Do 保证线程安全。原理是互斥锁。

输入:空 输出:全局唯一的单例

var once sync.Once


var dbConn *DB

func GetConnection() *DB {
    if dbConn == nil {
        once.Do(
            func() {
                dbConn = NewConnection()
            }
        )
    }
    return dbConn
}

抽象工厂模式

简单工厂模式

其实就是判断参数后区别创建对象,你可以视其为一个对象类选择器。里面往往是一堆条件语句。

输入:简单的参数 输出:复杂的对象。且返回的任何对象都属于同一接口。

var plugins []IPlugin

const userType = {
    AdminType
    MemberType
}
func main() {    
    var user IUser = NewUser(userType)
}

func NewUser(t userType) IUser {
    switch t {
    case AdminType:
        return &Admin{}
    }
    case MemberType:
        return &Member{}
    // ...
}

依赖关系:

main --> NewUser, userType, IUser

NewUser -> IUser, Admin, Member

这种情况下,具体的类对于用户依旧是可见的,并没有改善耦合度,只不过是把创建的代码提取出个方法而已。

工厂方法模式

工厂方法则是将创建过程下放到具体的类中:

var plugins []IPlugin

const userType = {
    AdminType
    MemberType
}
func main() {    
    var user = NewUser(userType)
}

func NewUser(t userType) IUser{
    switch t {
    case AdminType:
        return NewAdminType()
    }
    case MemberType:
        return NewMemberType()
    // ...
}

依赖关系:

main --> NewUser, userType, IUser
NewUser -> IUser, NewAdminType, NewMemberType
NewAdminType -> Admin
NewMemberType -> Member

可以看到,在 Go 中使用工厂方法模式,主程序间接依赖了所有。依旧不会降低耦合度,只是在简单工厂之上又做了一层方法提取,把创建过程下放罢了

抽象工厂方式

很简单,就是把工厂也变成接口。

var plugins []IPlugin

const userType = {
    AdminType
    MemberType
}

type ICreator func (t userType) IUser

func main() {    
    var creator ICreator = NewUser()
    creator.createUser(t)
}


func NewUser(t userType) IUser {
    switch t {
    case AdminType:
        return NewAdminType()
    }
    case MemberType:
        return NewMemberType()
    // ...
}

依赖关系

main -->  ICreator, IUser, (NewUser)
NewUserCreator -> NewUser, userType
NewUser -> NewAdminType, NewMemberType
NewAdminType -> Admin
NewMemberType -> Member

可以看到,抽象工厂主程序终于不直接依赖工厂了,耦合度终于有了实质性的降低。

但是仍旧需要约定大家都知道 userType。

生成器模式

一句话总结:通过返回生成器自身实现链式调用,通过完成函数返回最终对象。

例子:

builder := &ServerBuilder{}
builder
    .SetHost("127.0.0.1")
    .AddPort("80")
    .AddPort("443", keys)
    .Run()

原型模式

说白了就是实现克隆接口,从而即使不依赖具体实例的类也可以克隆它。

适配器模式

一句话总结:其实就是加一个中间层,从而让两边兼容。

典型例子:现有不同格式的配置文件类库,通过一个适配器使其能够自动调用适合的类库,读取出配置信息

桥接模式

其实还是接口。让一个函数的行为依赖于接口而不是实现,从而可以任意调整实现。

比如:我们需要一个函数检验用户输入的验证码是否正确,最初实现:

func check(userId int64, captcha string) bool {
    return myDb
        .from("tbl_captcha")
        .where("userId = ?", userId)
        .where("captcha = ?", captcha)
        .count() > 0
}

check(req.userId, req.captcha)

后来发现查数据库性能太差,需要能在用数据库和用 redis 之间切换,可以改成:


type IChecker func (int64, string) bool

var checker []IChecker= []{redisCaptchaCheck, dbCaptchaCheck}

func check(userId int64, captcha string) bool {
    checker[0](userId, captcha) // or checker[1](userId, captcha)
}

func redisCaptchaCheck(userId int64, captcha string) bool {
    return redisDb.get(tbl_captcha + "_" + userId + "_" + captcha).exists()
}

func dbCaptchaCheck(userId int64, captcha string) bool {
    return myDb
        .from("tbl_captcha")
        .where("userId = ?", userId)
        .where("captcha = ?", captcha)
        .count() > 0
}

这样,check 函数就不依赖于具体的检查函数,而是依赖于 IChecker 接口。具体的函数完全可以在外部定义。

组件模式

说白了就是含有自指指针的结构。

包括链表、二叉树就是组合模式的体现。它具有分形的特点。

type Node struct {
    Data  int    
    Left  *Node  
    Right *Node  
}

更加业务化的例子就是 Vue、React 里的组件。

外观模式

这也能整出个设计模式我是没料到的。其实就是提取代码,写流水账,把细节分到别处。

享元模式

这也能整出个设计模式…… 说白了就是共享指针。

举个例子吧,做 3D 程序时需要保存大量的顶点数据,而同类顶点往往只有坐标是不同的,其它数据都是相同的。享元模式相同的不各自存放,而是全部存放在一处,从而节省内存,并保证一致性。

链表模式

下面的几个模式本质上就是个链表,说句挨骂的话,结构没啥区别。不过由于用途不同,被赋予不同的名称和意义。

装饰器模式

说白了也是建造者,只不过建造者也同时是本体。

自我建造(修饰)的过程可以用函数实现

editor.getSelected()
    .setBold()
    .setFontSize(13)    

也可以用结构实现

var sel = editor.getSelected()
sel = BoldSetter(sel)
sel = FontSizeSetter(sel, 13)

典型的例子:中间件模式

var app = &App{}
app = &CorsMiddleware{in: app}
app = &AuthMiddleware{in: app}

代理模式

代理模式和装饰器模式是同构的,所以就结构而言,没有任何区别。

只是,代理模式更强调一个局外人接管了流程中的环节。装饰器模式更强调原本的功能得到了增强:

代理

var app     = &App{}
appWithAuth = &AuthMiddleware{next: app}
appWithCors = &CorsMiddleware{next: appWithAuth}

装饰器

var app     = &App{}
app         = &AuthMiddleware{next: app}
app         = &CorsMiddleware{next: app}

责任链模式

责任链又是一个是和装饰器同构的模式。你可以想象一组过滤器,每个过滤器能过滤掉一种物质。

流水线模式

其实就是责任链模式。不过一般说流水线,是线性的链,而责任链还包括树状的链。

命令模式

说白了就是多处重复的代码提取出来成为一个命令,大家要用就只要调这个命令就行了。出了 BUG 也只要改命令的实现部分。

在 WPF 中运用广泛。另外 React 的 Redux,Vue 的 Vuex,其 Action 就是命令。

迭代器模式

假设你有一个 100GB 的文件,你电脑的内存只有 32G,总不能全部读进来遍历吧?那么你可以逐个读取。这就是所谓迭代器。

package main

import (
    "bufio"
    "fmt"
    "strings"
)

func main() {
    in := `{"sample":"json string"}`

    s := bufio.NewScanner(strings.NewReader(in))
    s.Split(bufio.ScanRunes)

    for s.Scan() {
        fmt.Println(s.Text())
    }
}

调停模式

又叫中介模式,其实就是层次化管理。

比如你有一个程序要依赖 A,B,C…Z,那你可以分出子程序 0,负责管理 A~E, 1 负责管理 F~K…… 这样,需要直接负责的就少了。

和外观模式是同构的。

备忘录模式

说白了就是私自保存历史,从而可以恢复历史状态。

观察者模式

也叫发布订阅(pubsub)。这个模式非常有用。

成员:

  • 事件管理者
    • 管理通知
  • 订阅者
    • 接收通知
  • 发布者
    • 发布通知

引用

https://refactoringguru.cn/design-patterns/