当所需要创建的对象非常复杂时(go中的多层嵌套结构体实例化),就可以使用建造者模式,其可以:
1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。
2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。
3、对多个对象复用同样的对象创建逻辑。
实现一个建造者模式(在类Gorm框架中被广泛使用):
// 关键点1: 为ServiceProfile定义一个Builder对象type serviceProfileBuild struct {// 关键点2: 将ServiceProfile作为Builder的成员属性(把需要构建的作为其字段)profile *ServiceProfile}// 关键点3: 定义构建ServiceProfile的方法func (s *serviceProfileBuild) WithId(id string) *serviceProfileBuild {s.profile.Id = id//将内部字段赋值// 关键点4: 返回Builder接收者指针,支持链式调用return s}func (s *serviceProfileBuild) WithType(serviceType ServiceType) *serviceProfileBuild {s.profile.Type = serviceTypereturn s}// 关键点5: 定义Build方法,在链式调用的最后调用,返回构建好的ServiceProfilefunc (s *serviceProfileBuild) Build() *ServiceProfile {return s.profile}// 关键点6: 定义一个实例化Builder对象的工厂方法func NewServiceProfileBuilder() *serviceProfileBuild {return &serviceProfileBuild{profile: &ServiceProfile{}}}
使用者不需再直到对象具体的实现细节,直接进行链式调用即可,可读性,简洁性大大提升
Go特色的建造者模式
对于大型的结构体,上面的方式入参列表很长,并且具有很强的Java风格,因此建议使用更具Go风格的选项模式来优化,选项模式充分利用了go中函数是一等公民的特点,结合可变参数和闭包特性,可以实现更简洁的建造者模式
如:
// 关键点1: 定义构建ServiceProfile的functional option,以*ServiceProfile作为入参的函数type ServiceProfileOption func(profile *ServiceProfile)// 关键点2: 定义实例化ServiceProfile的工厂方法,使用ServiceProfileOption作为可变入参func NewServiceProfile(svcId string, svcType ServiceType, options ...ServiceProfileOption) *ServiceProfile {// 关键点3: 可为特定的字段提供默认值profile := &ServiceProfile{Id: svcId,Type: svcType,Status: Normal,Endpoint: network.EndpointOf("192.168.0.1", 80),Region: &Region{Id: "region1", Name: "beijing", Country: "China"},Priority: 1,Load: 100,}// 关键点4: 通过ServiceProfileOption来修改字段for _, option := range options {option(profile)//此处直接执行选项函数}return profile}// 关键点5: 定义一系列构建ServiceProfile的方法,在ServiceProfileOption实现构建逻辑,并返回ServiceProfileOptionfunc Status(status ServiceStatus) ServiceProfileOption {return func(profile *ServiceProfile) {//利用闭包实现选项函数profile.Status = status}}func Endpoint(ip string, port int) ServiceProfileOption {return func(profile *ServiceProfile) {profile.Endpoint = network.EndpointOf(ip, port)}}
//Uber规范中推荐将Option抽象为接口,使用接口实现选项模式
type Options interface {
apply(*Person)
}
//每个选项实现这个接口
type nameOpt struct {
name string
}
func (n *nameOpt) apply(opt *Person) {
opt.Name = n.name
}
type addrOpt struct {
addr string
}
func (a *addrOpt) apply(opt *Person) {
opt.Addr = a.addr
}
func WithN(n string) Options {
return &nameOpt{n}
}
func WithA(a string) Options {
return &addrOpt{a}
}
func NewP(opts ...Options) *Person {
p := DefaultPerson
for _, opt := range opts { //使用传入的各种选项的实例来执行其方法,实现修改p的目的
opt.apply(p)
}
return p
}
注: 一般选项函数的名称会以WithXXX命名
在以上两种建造者模式中,我们都没有限定属性的构建顺序,但是在特定的场景下,我们可能需要规定属性的构建顺序,可以利用接口,将每一步的方法都抽象为接口,每一个接口的方法返回下一步的接口
函数式选项模式更多应该应用在那些配置较多,且有可选参数的情况
建造者模式与抽象工厂类似,都用于构建复杂对象,但前者侧重点是对象的分布构建过程,后者则是构建对象/产品族
