建造者模式,也有翻译成生成器模式的,大家看到后知道他们是一个东西,都是Builer Pattern翻译过来的就行。它是一种对象构建模式,是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。 那么什么情况下适合使用建造模式呢?
当要构建的对象很大并且需要多个步骤时,使用构建器模式,有助于减小构造函数的大小。
我们先来看下其他语言里的 Builder,再看看 Go 怎么使用,进行个对比。
如果你是写过Java程序一定对下面这类代码很熟悉。
复制
Coffee.builder().name("Latti").price("30").build()
1.
当然,自己给Coffee类加上构建模式,还是需要写不少额外的代码,得给 Coffee 类加一个静态内部类 CoffeeBuilder,用CoffeeBuilder,去建造Coffee类的对象。
类、静态内部类傻傻分不清?可以看下小弟的 Java 文
光会面向对象基础做不了项目,还得掌握这些进阶知识
不过Java里有一个lombok包,只要引入这个包再在实体类加上@Builder注解,就可以使用建造模式构建对象啦。
复制
import lombok.Builder;@Builder public class Coffee extends BaseEntity implements Serializable {private String name;private Long price; ......}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
那在Go里面要怎么实现Builder模式呢?仿照上面这个模式,我们可以这样:
假设我们要在项目里搞个 DB 链接池,连接池提供了很多配置化的参数。
复制
type DBPool struct {dsn string maxOpenConn intmaxIdleConn int... maxConnLifeTime time.Duration}
1.
2.
3.
4.
5.
6.
7.
我们给 DB 连接池加一个建造者模式,这样在设置每个配置化参数的时候就可以对参数进行一步检查,避免直接 new 连接池对象,再给每个属性赋值时都加判断,把每个参数的校验内聚到参数自己的建造者步骤里。
复制
type DBPoolBuilder struct {DBPool err error}func Builder () *DBPoolBuilder {b := new(DBPoolBuilder)// 设置 DBPool 属性的默认值 b.DBPool.dsn = "127.0.0.1:3306"b.DBPool.maxConnLifeTime = 1 * time.Secondb.DBPool.maxOpenConn = 30return b}func (b *DBPoolBuilder) DSN(dsn string) *DBPoolBuilder {if b.err != nil {return b}if dsn == "" {b.err = fmt.Errorf("invalid dsn, current is %s", dsn)} b.DBPool.dsn = dsn return b}func (b *DBPoolBuilder) MaxOpenConn(connNum int) *DBPoolBuilder {if b.err != nil {return b}if connNum < 1 {b.err = fmt.Errorf("invalid MaxOpenConn, current is %d", connNum)} b.DBPool.maxOpenConn = connNum return b}func (b *DBPoolBuilder) MaxConnLifeTime(lifeTime time.Duration) *DBPoolBuilder {if b.err != nil {return b}if lifeTime < 1 * time.Second {b.err = fmt.Errorf("connection max life time can not litte than 1 second, current is %v", lifeTime)} b.DBPool.maxConnLifeTime = lifeTime return b}func (b *DBPoolBuilder) Build() (*DBPool, error) {if b.err != nil {return nil, b.err}if b.DBPool.maxOpenConn < b.DBPool.maxIdleConn {return nil, fmt.Errorf("max total(%d) cannot < max idle(%d)", b.DBPool.maxOpenConn, b.DBPool.maxIdleConn)}return &b.DBPool, nil}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
接下来就可以使用构建模式创造DBPool类型的对象了。
复制
package main import "xxx/dbpool"func main() {dbPool, err := dbpool.Builder().DSN("localhost:3306").MaxOpenConn(50).MaxConnLifeTime(0 * time.Second).Build()if err != nil {fmt.Println(err)}fmt.Println(dbPool)}
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
另外在建造者过程的每个参数步骤里,我们都借用了之前提到的处理 Go Error 的方式,把在外部调用时的错误判断,分散到了每个步骤里。
这么一来有从观感上觉得确实比定义一个参数巨多的 DBPool 构造函数要好一点。你觉得呢?
Go 里边还有一个函数时编程风格,利用的是函数的可变参数 (variadic parameters) ,这种编程模式就是 Option 模式。