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)。这个模式非常有用。
成员:
-
事件管理者
- 管理通知
-
订阅者
- 接收通知
-
发布者
- 发布通知