Go设计模式03-建造者模式

注:本文已发布超过一年,请注意您所使用工具的相关版本是否适用

笔记

代码实现

其实在 Golang 中对于创建类参数比较多的对象的时候,我们常见的做法是必填参数直接传递,可选参数通过传递可变的方法进行创建。
本文会先实现课程中的建造者模式,然后再实现我们常用的方式。

建造者模式

代码

通过下面可以看到,使用 Go 编写建造者模式的代码其实会很长,这些是它的一个缺点,所以如果不是参数的校验逻辑很复杂的情况下一般我们在 Go 中不会采用这种方式,而会采用后面的另外一种方式

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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
package builder

import "fmt"

const (
defaultMaxTotal = 10
defaultMaxIdle = 9
defaultMinIdle = 1
)

// ResourcePoolConfig resource pool
type ResourcePoolConfig struct {
name string
maxTotal int
maxIdle int
minIdle int
}

// ResourcePoolConfigBuilder 用于构建 ResourcePoolConfig
type ResourcePoolConfigBuilder struct {
name string
maxTotal int
maxIdle int
minIdle int
}

// SetName SetName
func (b *ResourcePoolConfigBuilder) SetName(name string) error {
if name == "" {
return fmt.Errorf("name can not be empty")
}
b.name = name
return nil
}

// SetMinIdle SetMinIdle
func (b *ResourcePoolConfigBuilder) SetMinIdle(minIdle int) error {
if minIdle < 0 {
return fmt.Errorf("max tatal cannot < 0, input: %d", minIdle)
}
b.minIdle = minIdle
return nil
}

// SetMaxIdle SetMaxIdle
func (b *ResourcePoolConfigBuilder) SetMaxIdle(maxIdle int) error {
if maxIdle < 0 {
return fmt.Errorf("max tatal cannot < 0, input: %d", maxIdle)
}
b.maxIdle = maxIdle
return nil
}

// SetMaxTotal SetMaxTotal
func (b *ResourcePoolConfigBuilder) SetMaxTotal(maxTotal int) error {
if maxTotal <= 0 {
return fmt.Errorf("max tatal cannot <= 0, input: %d", maxTotal)
}
b.maxTotal = maxTotal
return nil
}

// Build Build
func (b *ResourcePoolConfigBuilder) Build() (*ResourcePoolConfig, error) {
if b.name == "" {
return nil, fmt.Errorf("name can not be empty")
}

// 设置默认值
if b.minIdle == 0 {
b.minIdle = defaultMinIdle
}

if b.maxIdle == 0 {
b.maxIdle = defaultMaxIdle
}

if b.maxTotal == 0 {
b.maxTotal = defaultMaxTotal
}

if b.maxTotal < b.maxIdle {
return nil, fmt.Errorf("max total(%d) cannot < max idle(%d)", b.maxTotal, b.maxIdle)
}

if b.minIdle > b.maxIdle {
return nil, fmt.Errorf("max idle(%d) cannot < min idle(%d)", b.maxIdle, b.minIdle)
}

return &ResourcePoolConfig{
name: b.name,
maxTotal: b.maxTotal,
maxIdle: b.maxIdle,
minIdle: b.minIdle,
}, 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
package builder

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestResourcePoolConfigBuilder_Build(t *testing.T) {
tests := []struct {
name string
builder *ResourcePoolConfigBuilder
want *ResourcePoolConfig
wantErr bool
}{
{
name: "name empty",
builder: &ResourcePoolConfigBuilder{
name: "",
maxTotal: 0,
},
want: nil,
wantErr: true,
},
{
name: "maxIdle < minIdle",
builder: &ResourcePoolConfigBuilder{
name: "test",
maxTotal: 0,
maxIdle: 10,
minIdle: 20,
},
want: nil,
wantErr: true,
},
{
name: "success",
builder: &ResourcePoolConfigBuilder{
name: "test",
},
want: &ResourcePoolConfig{
name: "test",
maxTotal: defaultMaxTotal,
maxIdle: defaultMaxIdle,
minIdle: defaultMinIdle,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.builder.Build()
require.Equalf(t, tt.wantErr, err != nil, "Build() error = %v, wantErr %v", err, tt.wantErr)
assert.Equal(t, tt.want, got)
})
}
}

Go 常用的参数传递方法

代码

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
package builder

import "fmt"

// ResourcePoolConfigOption option
type ResourcePoolConfigOption struct {
maxTotal int
maxIdle int
minIdle int
}

// ResourcePoolConfigOptFunc to set option
type ResourcePoolConfigOptFunc func(option *ResourcePoolConfigOption)

// NewResourcePoolConfig NewResourcePoolConfig
func NewResourcePoolConfig(name string, opts ...ResourcePoolConfigOptFunc) (*ResourcePoolConfig, error) {
if name == "" {
return nil, fmt.Errorf("name can not be empty")
}

option := &ResourcePoolConfigOption{
maxTotal: 10,
maxIdle: 9,
minIdle: 1,
}

for _, opt := range opts {
opt(option)
}

if option.maxTotal < 0 || option.maxIdle < 0 || option.minIdle < 0 {
return nil, fmt.Errorf("args err, option: %v", option)
}

if option.maxTotal < option.maxIdle || option.minIdle > option.maxIdle {
return nil, fmt.Errorf("args err, option: %v", option)
}

return &ResourcePoolConfig{
name: name,
maxTotal: option.maxTotal,
maxIdle: option.maxIdle,
minIdle: option.minIdle,
}, 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
package builder

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestNewResourcePoolConfig(t *testing.T) {
type args struct {
name string
opts []ResourcePoolConfigOptFunc
}
tests := []struct {
name string
args args
want *ResourcePoolConfig
wantErr bool
}{
{
name: "name empty",
args: args{
name: "",
},
want: nil,
wantErr: true,
},
{
name: "success",
args: args{
name: "test",
opts: []ResourcePoolConfigOptFunc{
func(option *ResourcePoolConfigOption) {
option.minIdle = 2
},
},
},
want: &ResourcePoolConfig{
name: "test",
maxTotal: 10,
maxIdle: 9,
minIdle: 2,
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := NewResourcePoolConfig(tt.args.name, tt.args.opts...)
require.Equalf(t, tt.wantErr, err != nil, "error = %v, wantErr %v", err, tt.wantErr)
assert.Equal(t, tt.want, got)
})
}
}

总结

其实可以看到,绝大多数情况下直接使用后面的这种方式就可以了,并且在编写公共库的时候,强烈建议入口的参数都可以这么传递,这样可以最大程度的保证我们公共库的兼容性,避免在后续的更新的时候出现破坏性的更新的情况。

关注我获取更新

wechat
知乎
github

猜你喜欢