定义

将一个复杂对象的构建与它的表示分离,使得同样的构建 过程可以创建不同的表示。

复杂的构造函数

创建一个对象最常用的方式是,使用 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, 20false 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)

但是,这里还是有几个问题:

  1. 如果必填的配置项有很多,那构造函数就又会出现参数列表很长的问题。
  2. 如果配置项之间有一定的依赖关系,并校验参数的合法性。
  3. 如果希望ResourcePool的配置项不对外提供修改方法。

使用构造者重构

  1. 首先,创建一个构造者类Builder,并把参数改为私有,提供set()函数修改。
  2. 其次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()处理构造类之前的一些逻辑。如此,可以以通过设置不同的可选参数,“定制化”地创建不同的复杂对象.

参考