Golang 设计模式

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

单例模式

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

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

 1var once sync.Once
 2
 3
 4var dbConn *DB
 5
 6func GetConnection() *DB {
 7    if dbConn == nil {
 8        once.Do(
 9            func() {
10                dbConn = NewConnection()
11            }
12        )
13    }
14    return dbConn
15}

抽象工厂模式

简单工厂模式

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

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

 1var plugins []IPlugin
 2
 3const userType = {
 4    AdminType
 5    MemberType
 6}
 7func main() {    
 8    var user IUser = NewUser(userType)
 9}
10
11func NewUser(t userType) IUser {
12    switch t {
13    case AdminType:
14        return &Admin{}
15    }
16    case MemberType:
17        return &Member{}
18    // ...
19}

依赖关系:

main --> NewUser, userType, IUser

NewUser -> IUser, Admin, Member

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

工厂方法模式

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

 1var plugins []IPlugin
 2
 3const userType = {
 4    AdminType
 5    MemberType
 6}
 7func main() {    
 8    var user = NewUser(userType)
 9}
10
11func NewUser(t userType) IUser{
12    switch t {
13    case AdminType:
14        return NewAdminType()
15    }
16    case MemberType:
17        return NewMemberType()
18    // ...
19}

依赖关系:

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

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

抽象工厂方式

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

 1var plugins []IPlugin
 2
 3const userType = {
 4    AdminType
 5    MemberType
 6}
 7
 8type ICreator func (t userType) IUser
 9
10func main() {    
11    var creator ICreator = NewUser()
12    creator.createUser(t)
13}
14
15
16func NewUser(t userType) IUser {
17    switch t {
18    case AdminType:
19        return NewAdminType()
20    }
21    case MemberType:
22        return NewMemberType()
23    // ...
24}

依赖关系

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

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

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

生成器模式

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

例子:

1builder := &ServerBuilder{}
2builder
3    .SetHost("127.0.0.1")
4    .AddPort("80")
5    .AddPort("443", keys)
6    .Run()

原型模式

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

适配器模式

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

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

桥接模式

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

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

1func check(userId int64, captcha string) bool {
2    return myDb
3        .from("tbl_captcha")
4        .where("userId = ?", userId)
5        .where("captcha = ?", captcha)
6        .count() > 0
7}
8
9check(req.userId, req.captcha)

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

 1
 2type IChecker func (int64, string) bool
 3
 4var checker []IChecker= []{redisCaptchaCheck, dbCaptchaCheck}
 5
 6func check(userId int64, captcha string) bool {
 7    checker[0](userId, captcha) // or checker[1](userId, captcha)
 8}
 9
10func redisCaptchaCheck(userId int64, captcha string) bool {
11    return redisDb.get(tbl_captcha + "_" + userId + "_" + captcha).exists()
12}
13
14func dbCaptchaCheck(userId int64, captcha string) bool {
15    return myDb
16        .from("tbl_captcha")
17        .where("userId = ?", userId)
18        .where("captcha = ?", captcha)
19        .count() > 0
20}

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

组件模式

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

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

1type Node struct {
2    Data  int    
3    Left  *Node  
4    Right *Node  
5}

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

外观模式

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

享元模式

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

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

链表模式

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

装饰器模式

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

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

1editor.getSelected()
2    .setBold()
3    .setFontSize(13)    

也可以用结构实现

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

典型的例子:中间件模式

1var app = &App{}
2app = &CorsMiddleware{in: app}
3app = &AuthMiddleware{in: app}

代理模式

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

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

代理

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

装饰器

1var app     = &App{}
2app         = &AuthMiddleware{next: app}
3app         = &CorsMiddleware{next: app}

责任链模式

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

流水线模式

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

命令模式

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

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

迭代器模式

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

 1package main
 2
 3import (
 4    "bufio"
 5    "fmt"
 6    "strings"
 7)
 8
 9func main() {
10    in := `{"sample":"json string"}`
11
12    s := bufio.NewScanner(strings.NewReader(in))
13    s.Split(bufio.ScanRunes)
14
15    for s.Scan() {
16        fmt.Println(s.Text())
17    }
18}

调停模式

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

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

和外观模式是同构的。

备忘录模式

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

观察者模式

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

成员:

  • 事件管理者

    • 管理通知
  • 订阅者

    • 接收通知
  • 发布者

    • 发布通知

引用

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