当所需要创建的对象非常复杂时(go中的多层嵌套结构体实例化),就可以使用建造者模式,其可以:

1、封装复杂对象的创建过程,使对象使用者不感知复杂的创建逻辑。
2、可以一步步按照顺序对成员进行赋值,或者创建嵌套对象,并最终完成目标对象的创建。
3、对多个对象复用同样的对象创建逻辑。

实现一个建造者模式(在类Gorm框架中被广泛使用):

  1. // 关键点1: 为ServiceProfile定义一个Builder对象
  2. type serviceProfileBuild struct {
  3. // 关键点2: 将ServiceProfile作为Builder的成员属性(把需要构建的作为其字段)
  4. profile *ServiceProfile
  5. }
  6. // 关键点3: 定义构建ServiceProfile的方法
  7. func (s *serviceProfileBuild) WithId(id string) *serviceProfileBuild {
  8. s.profile.Id = id//将内部字段赋值
  9. // 关键点4: 返回Builder接收者指针,支持链式调用
  10. return s
  11. }
  12. func (s *serviceProfileBuild) WithType(serviceType ServiceType) *serviceProfileBuild {
  13. s.profile.Type = serviceType
  14. return s
  15. }
  16. // 关键点5: 定义Build方法,在链式调用的最后调用,返回构建好的ServiceProfile
  17. func (s *serviceProfileBuild) Build() *ServiceProfile {
  18. return s.profile
  19. }
  20. // 关键点6: 定义一个实例化Builder对象的工厂方法
  21. func NewServiceProfileBuilder() *serviceProfileBuild {
  22. return &serviceProfileBuild{profile: &ServiceProfile{}}
  23. }

使用者不需再直到对象具体的实现细节,直接进行链式调用即可,可读性,简洁性大大提升

Go特色的建造者模式

对于大型的结构体,上面的方式入参列表很长,并且具有很强的Java风格,因此建议使用更具Go风格的选项模式来优化,选项模式充分利用了go中函数是一等公民的特点,结合可变参数和闭包特性,可以实现更简洁的建造者模式

如:

  1. // 关键点1: 定义构建ServiceProfile的functional option,以*ServiceProfile作为入参的函数
  2. type ServiceProfileOption func(profile *ServiceProfile)
  3. // 关键点2: 定义实例化ServiceProfile的工厂方法,使用ServiceProfileOption作为可变入参
  4. func NewServiceProfile(svcId string, svcType ServiceType, options ...ServiceProfileOption) *ServiceProfile {
  5. // 关键点3: 可为特定的字段提供默认值
  6. profile := &ServiceProfile{
  7. Id: svcId,
  8. Type: svcType,
  9. Status: Normal,
  10. Endpoint: network.EndpointOf("192.168.0.1", 80),
  11. Region: &Region{Id: "region1", Name: "beijing", Country: "China"},
  12. Priority: 1,
  13. Load: 100,
  14. }
  15. // 关键点4: 通过ServiceProfileOption来修改字段
  16. for _, option := range options {
  17. option(profile)//此处直接执行选项函数
  18. }
  19. return profile
  20. }
  21. // 关键点5: 定义一系列构建ServiceProfile的方法,在ServiceProfileOption实现构建逻辑,并返回ServiceProfileOption
  22. func Status(status ServiceStatus) ServiceProfileOption {
  23. return func(profile *ServiceProfile) {//利用闭包实现选项函数
  24. profile.Status = status
  25. }
  26. }
  27. func Endpoint(ip string, port int) ServiceProfileOption {
  28. return func(profile *ServiceProfile) {
  29. profile.Endpoint = network.EndpointOf(ip, port)
  30. }
  31. }
//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命名
在以上两种建造者模式中,我们都没有限定属性的构建顺序,但是在特定的场景下,我们可能需要规定属性的构建顺序,可以利用接口,将每一步的方法都抽象为接口,每一个接口的方法返回下一步的接口
函数式选项模式更多应该应用在那些配置较多,且有可选参数的情况
建造者模式与抽象工厂类似,都用于构建复杂对象,但前者侧重点是对象的分布构建过程,后者则是构建对象/产品族