建造者模式是一种创建型设计模式,目的是将一个复杂对象的构建过程与其表示相分离,从而可以创建不同表示形式的对象。
基本介绍
意图
将一个复杂的构建过程和表示相分离,使得同样的构建过程可以创建不同的表示
适用场景
在软件系统中,一个复杂对象的创建通常由多个部分组成,这些部分的组合经常变化,但是组合的算法相对稳定。
建造者模式通过将变与不变的部分分开,来解决这个问题。
-
类的属性比较多
-
类的属性之间有一定的依赖关系或约束条件
-
存在必选和非必选的属性
-
希望创建不可变的对象
关键代码
-
建造者:创建并提供实例
-
导演:管理建造出来的实例的依赖关系和控制构建过程
应用实例
-
肯德基里,汉堡、可乐、薯条、鸡翅等商品是不变的,组合是经常变化的,生成不同的套餐
-
Java的StringBuilder
优点
-
分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。
-
可以更好地控制构建过程,隐藏具体构建细节。
-
代码复用性高,可以在不同的构建过程中重复使用相同的建造者。
缺点
- 如果产品的属性较少,建造者模式可能会导致代码冗余。
- 增加了系统的类和对象数量。
## 使用场景
- 需要生成的对象具有复杂的内部结构。
- 需要生成的对象内部属性相互依赖。
和工厂模式的区别:建造者模式更加关注零件装配的顺序,一般用于创建参数复杂的对象;工厂模式用于创建类型相关的不同对象
结构
主要包含下面几个主要角色
-
产品:要构建的复杂对象,通常包含多个部分或属性
-
抽象建造者:定义了构建产品的抽象接口,包括构建产品的各个部分的方法
-
具体建造者:实现抽象建造者接口,具体确定如何构建产品的各个部分,并负责返回最终构建的产品
-
指导者:负责调用建造者的方法来构建产品,不了解具体的构建过程,只关心产品的构建顺序和方式
代码实例
使用Go编写建造者模式的代码其实会很长,这是它的一个缺点,如果不是参数校验逻辑很复杂的情况下一般采用第二种方式
const (
defaultMaxTotal = 10
defaultMaxIdle = 9
defaultMinIdle = 1
)
type ResourcePoolConfig struct {
name string
maxTotal int
maxIdle int
minIdle int
}
// 用于构建 ResourcePoolConfig
type ResourcePoolConfigBuilder struct {
name string
maxTotal int
maxIdle int
minIdle int
}
func (r *ResourcePoolConfigBuilder) SetName(name string) error {
if len(name) == 0 {
return fmt.Errorf("name can not be empty")
}
r.name = name
return nil
}
func (r *ResourcePoolConfigBuilder) SetMaxTotal(maxTotal int) error {
if maxTotal <= 0 {
return fmt.Errorf("max total cannot <= 0, input: %d", maxTotal)
}
r.maxTotal = maxTotal
return nil
}
func (r *ResourcePoolConfigBuilder) SetMaxIdle(maxIdle int) error {
if maxIdle < 0 {
return fmt.Errorf("max idle cannot < 0, input: %d", maxIdle)
}
r.maxIdle = maxIdle
return nil
}
func (r *ResourcePoolConfigBuilder) SetMinIdle(minIdle int) error {
if minIdle < 0 {
return fmt.Errorf("max idle cannot < 0, input: %d", minIdle)
}
r.minIdle = minIdle
return nil
}
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
}
第二种方式通过可变参数的方式来传递入口的参数,感觉也很容易扩展,兼容性高
func Test_NewBuilder(t *testing.T) {
opts := []ResourcePoolConfigOptFunc{
func(option *ResourcePoolConfigOption) {
option.maxTotal = 20
},
}
config, err := NewResourcePoolConfig("test", opts...)
if err != nil {
t.Fatal(err)
}
t.Logf("%+v", config) //&{name:test maxTotal:20 maxIdle:9 minIdle:1}
}