建造者模式

98 阅读4分钟

建造者模式也是创建型模式,用来创建对象,适用的场景如下:

  • 我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
  • 如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
  • 如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了

总结一句话,如果我们想要达到两点

1.优雅的设置必填属性即用set()方法
2.需要检验属性值

就用builder包一层,先把属性set给builder,builder统一校验完之后,在把自己的属性赋值给真正的结构体

针对上面的问题,建造者模式可以解决

工厂模式,是由工厂类来负责对象创建的工作;建造者模式是让建造者类来负责对象的创建工作,实现上除了配置类之外还需要一个builder建造类,builder类成员变量和配置类一样,先创建builder类,在builder类中对变量赋值(通过builder的setXXX方法),然后调用builder类的方法build()构建配置类,返回的对象是配置类,具体实现如下

// 配置类
type Config struct {
   Name     string
   MaxTotal int
   MaxIdle  int
   MinIdle  int
}

// 方法小写,外部不可调用,只能在builder类的Build方法中校验逻辑通过之后调用,把builder类的成员变量赋值给配置类
func initCfg(b *builder) *Config {
   return &Config{
      Name:     b.Name,
      MaxIdle:  b.MaxIdle,
      MinIdle:  b.MinIdle,
      MaxTotal: b.MaxTotal,
   }
}

// 默认的成员变量值,有些变量允许不显式赋值,就赋给默认值
var (
   defaultMaxTotal int = 10
   defaultMinIdle  int = 12
   defaultMaxIdle  int = 15
)

// 建造类,和配置类有一样的成员变量
type builder struct {
   Name     string
   MaxTotal int
   MaxIdle  int
   MinIdle  int
}

// 初始化builder类,默认值在此配置
func InitBuilder() *builder {
   return &builder{
      MaxTotal: defaultMaxTotal,
      MaxIdle:  defaultMaxIdle,
      MinIdle:  defaultMinIdle,
   }
}

// 建造类的SetXXX方法,给使用者提供设置变量的方法
func (b *builder) SetName(s string) *builder {
   if s == "" {
      panic("Name nil")
      return nil
   }
   b.Name = s
   return b
}

func (b *builder) SetMaxTotal(s int) *builder {
   if s == 0 {
      panic("val nil")
      return nil
   }
   b.MaxTotal = s
   return b
}
func (b *builder) SetMaxIdle(s int) *builder {
   if s == 0 {
      panic("val nil")
      return nil
   }
   b.MaxIdle = s
   return b
}
func (b *builder) SetMinIdle(s int) *builder {
   if s == 0 {
      panic("val nil")
      return nil
   }
   b.MinIdle = s
   return b
}

// builder类的校验逻辑在此,校验失败返回,成功调用初始化配置类的方法,返回配置类
func (b *builder) Build() (c *Config, err error) {
   //check wrong
   if b.Name == "" {
      return nil, fmt.Errorf("Name nil")
   }
   if b.MaxTotal > 20 {
      return nil, fmt.Errorf("maxtotal dont have ")
   }
   if b.MinIdle < 3 {
      return nil, fmt.Errorf("MinIdle err")
   }

   //校验通过,初始化配置类
   c = initCfg(b)
   return c, nil
}

// example
func test() {
   cfg, err := InitBuilder().SetName("bart").SetMaxIdle(30).SetMinIdle(20).Build()
   if err != nil {
      fmt.Println("err:", err)
   } else {
      fmt.Println(cfg)
   }
}

可以将上面的代码拆解一下,实现建造者模式需要有这几个部分:

建造者类需要有:

  • 默认值,有些变量不是必填量,要给默认值,如果没有,就忽略
  • 建造者结构体builder,里面的成员变量和配置类一样
  • builder类的设置成员变量的SetXXX方法,哪些变量允许用户设置就提供该方法
  • builder类校验成员赋值情况的Build方法,返回是配置类

配置类需要一个结构体即可,没有对应方法,这样就达到只能通过builder类创建对象的目的

另外还需要一个方法initCfg,输入是builder类对象,输出是配置类,供builder类的Build方法调用,这个方法小写,外部不可调用

有了上面这几个部分,就完成的建造者模式的实现,构造者模式有个缺点就是同样的变量要在builder类和配置类里面设置两次,这样有点重复;但是好处是配置类没有失效状态,要么没有,要么就是已经校验好的正常实例,不会存在一个实例,变量不满足要求的,因为builder类已经校验过了

(感觉这种设计模式可以用在一些请求接口请求参数很多,并且有些需要校验的,校验完成之后输出一个完整的配置类,这样才能继续下面的业务逻辑)