定义
将一个复杂对象的构建与它的表示分离,使得同样的构建 过程可以创建不同的表示。
复杂的构造函数
创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。举个常用资源连接池的例子
type ResourcePool struct {
name string
maxTotal int
maxIdle int
minIdle int
}
resourcePool := NewResourcePool("dbconnectionpool", 16, 8,
10)
在可配置项不多的时候没什么问题,但是,如果可配置项逐渐增多,以gorm的配置文件为例
// Config GORM config
type Config struct {
// GORM perform single create, update, delete operations in transactions by default to ensure database data integrity
// You can disable it by setting `SkipDefaultTransaction` to true
SkipDefaultTransaction bool
// NamingStrategy tables, columns naming strategy
NamingStrategy schema.Namer
// FullSaveAssociations full save associations
FullSaveAssociations bool
// Logger
Logger logger.Interface
// NowFunc the function to be used when creating a new timestamp
NowFunc func() time.Time
// DryRun generate sql without execute
DryRun bool
// PrepareStmt executes the given query in cached statement
PrepareStmt bool
// DisableAutomaticPing
DisableAutomaticPing bool
// DisableForeignKeyConstraintWhenMigrating
DisableForeignKeyConstraintWhenMigrating bool
// DisableNestedTransaction disable nested transaction
DisableNestedTransaction bool
// AllowGlobalUpdate allow global update
AllowGlobalUpdate bool
// QueryFields executes the SQL query with all fields of the table
QueryFields bool
// CreateBatchSize default create batch size
CreateBatchSize int
// ClauseBuilders clause builder
ClauseBuilders map[string]clause.ClauseBuilder
// ConnPool db conn pool
ConnPool ConnPool
// Dialector database dialector
Dialector
// Plugins registered plugins
Plugins map[string]Plugin
// ... 省略一些参数
}
这时,这个构造函数就变得复杂了。构造函数参数列表会变得很长,容易搞错各参数的顺序,造成隐蔽的bug。
resourcePool := NewResourcePool("dbconnectionpool", 16, null, 8, null, false , true, 10, 20,false, true )
开始重构,这里使用 set() 函数来给成员变量赋值,以替代冗长的构造函数,并在set()函数里做参数校验。
func NewResourcePool(name string) *ResourcePool {
this := &ResourcePool{}
this.SetName(name)
return this
}
func (this *ResourcePool) SetName(name string) *ResourcePool {
if len(name) <= 0 {
panic(any("name should not be empty."))
}
this.name = name
return this
}
func (this *ResourcePool) SetMaxTotal(maxTotal int) *ResourcePool {
if maxTotal <= 0 {
panic(any("maxTotal should be positive."))
}
this.maxTotal = maxTotal
return this
}
func (this *ResourcePool) SetMinIdle(minIdle int) *ResourcePool {
if minIdle < 0 {
panic(any("minIdle should not be negative."))
}
this.minIdle = minIdle
return this
}
重构后的构造函数:
resourcePool := NewResourcePool("dbconnectionpool").SetMaxTotal(16).SetMinIdle(8)
但是,这里还是有几个问题:
- 如果必填的配置项有很多,那构造函数就又会出现参数列表很长的问题。
- 如果配置项之间有一定的依赖关系,并校验参数的合法性。
- 如果希望ResourcePool的配置项不对外提供修改方法。
使用构造者重构
- 首先,创建一个构造者类Builder,并把参数改为私有,提供set()函数修改。
- 其次build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。
func NewResourcePool(builder *ResourcePoolBuilder) *ResourcePool {
this := &ResourcePool{}
this.name = builder.name
this.maxTotal = builder.maxTotal
this.maxIdle = builder.maxIdle
this.minIdle = builder.minIdle
return this
}
type ResourcePoolBuilder struct {
name string
maxTotal int
maxIdle int
minIdle int
}
func Builder() *ResourcePoolBuilder {
builder := &ResourcePoolBuilder{}
builder.maxTotal = 16
builder.minIdle = 5
return builder
}
func (b *ResourcePoolBuilder) Build() *ResourcePool {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if len(b.name) <= 0 {
panic(any("name should not be empty."))
}
if b.maxIdle > b.maxTotal {
panic(any("maxIdle should bigger than maxTotal."))
}
return NewResourcePool(b)
}
func (this *ResourcePoolBuilder) SetName(name string) *ResourcePoolBuilder {
if len(name) <= 0 {
panic(any("name should not be empty."))
}
this.name = name
return this
}
func (this *ResourcePoolBuilder) SetMaxTotal(maxTotal int) *ResourcePoolBuilder {
if maxTotal <= 0 {
panic(any("maxTotal should be positive."))
}
this.maxTotal = maxTotal
return this
}
func (this *ResourcePoolBuilder) SetMinIdle(minIdle int) *ResourcePoolBuilder {
if minIdle < 0 {
panic(any("minIdle should not be negative."))
}
this.minIdle = minIdle
return this
}
重构后的构造函数
resourcePool := Builder().SetName("test").SetMaxTotal(0).SetMinIdle(5).Build()
如此,
- 这样我们就只能通过建造者Builder 来创建 ResourcePool 类对象。
- 要修改resourcePool只能通过 Builder提供的 set()函数,配置项不对外提供修改方法。
- 参数校验或者提供默认参数可以放在build()函数内。
小结
构造者模式原理并不复杂,主要适当的场景中灵活使用。通过构造者类Builder、提供的set()函数设置必选项,最终调用build()处理构造类之前的一些逻辑。如此,可以以通过设置不同的可选参数,“定制化”地创建不同的复杂对象.