主页

索引

模块索引

搜索页面

Builder-建造者

备注

constructs complex objects by separating construction and representation.用来创建复杂对象,可以通过设置不同的可选参数,“定制化” 地创建不同的对象。

https://img.zhaoweiguo.com/knowledge/images/architectures/design-modes/Creationals/builder1.png

备注

In Go we might use the “functional Options” idiom, whereas in Java we might use a builder pattern.

遇到问题

在程序设计中,我们会经常遇到一些复杂的对象,其中有很多成员属性,甚至嵌套着多个复杂的对象。这种情况下,创建这个复杂对象就会变得很繁琐。

对于 C++/Java 而言,最常见的表现就是构造函数有着长长的参数列表:

MyObject obj = new MyObject(param1, param2, param3, param4, param5, param6, ...)

而对于 Go 语言来说,最常见的表现就是多层的嵌套实例化:

obj := &MyObject{
  Field1: &Field1 {
    Param1: &Param1 {
      Val: 0,
    },
    Param2: &Param2 {
      Val: 1,
    },
    ...
  },
  Field2: &Field2 {
    Param3: &Param3 {
      Val: 2,
    },
    ...
  },
  ...
}

上述的对象创建方法有两个明显的缺点:

1. 对对象使用者不友好,使用者在创建对象时需要知道的细节太多
2. 代码可读性很差。

示例

一个 Message 结构体,其主要有 Header 和 Body 组成:

package msg
...
type Message struct {
  Header *Header
  Body   *Body
}
type Header struct {
  SrcAddr  string
  SrcPort  uint64
  DestAddr string
  DestPort uint64
  Items    map[string]string
}
type Body struct {
  Items []string
}
...

按照直接的对象创建方式,创建逻辑应该是这样的:

// 多层的嵌套实例化
message := msg.Message{
  Header: &msg.Header{
    SrcAddr:  "192.168.0.1",
    SrcPort:  1234,
    DestAddr: "192.168.0.2",
    DestPort: 8080,
    Items:    make(map[string]string),
  },
  Body:   &msg.Body{
    Items: make([]string, 0),
  },
}
// 需要知道对象的实现细节
message.Header.Items["contents"] = "application/json"
message.Body.Items = append(message.Body.Items, "record1")
message.Body.Items = append(message.Body.Items, "record2")

建造者模式

建造者模式的作用有如下几个:

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

引入建造者模式对代码进行重构:

package msg
...
// Message对象的Builder对象
type builder struct {
  once *sync.Once
  msg *Message
}
// 返回Builder对象
func Builder() *builder {
  return &builder{
    once: &sync.Once{},
    msg: &Message{Header: &Header{}, Body: &Body{}},
  }
}
// 以下是对Message成员对构建方法
func (b *builder) WithSrcAddr(srcAddr string) *builder {
  b.msg.Header.SrcAddr = srcAddr
  return b
}
func (b *builder) WithSrcPort(srcPort uint64) *builder {
  b.msg.Header.SrcPort = srcPort
  return b
}
func (b *builder) WithDestAddr(destAddr string) *builder {
  b.msg.Header.DestAddr = destAddr
  return b
}
func (b *builder) WithDestPort(destPort uint64) *builder {
  b.msg.Header.DestPort = destPort
  return b
}
func (b *builder) WithHeaderItem(key, value string) *builder {
  // 保证map只初始化一次
  b.once.Do(func() {
    b.msg.Header.Items = make(map[string]string)
  })
  b.msg.Header.Items[key] = value
  return b
}
func (b *builder) WithBodyItem(record string) *builder {
  b.msg.Body.Items = append(b.msg.Body.Items, record)
  return b
}
// 创建Message对象,在最后一步调用
func (b *builder) Build() *Message {
  return b.msg
}

测试代码如下:

package test
...
func TestMessageBuilder(t *testing.T) {
  // 使用消息建造者进行对象创建
  message := msg.Builder().
    WithSrcAddr("192.168.0.1").
    WithSrcPort(1234).
    WithDestAddr("192.168.0.2").
    WithDestPort(8080).
    WithHeaderItem("contents", "application/json").
    WithBodyItem("record1").
    WithBodyItem("record2").
    Build()
  if message.Header.SrcAddr != "192.168.0.1" {
    t.Errorf("expect src address 192.168.0.1, but actual %s.", message.Header.SrcAddr)
  }
  if message.Body.Items[0] != "record1" {
    t.Errorf("expect body item0 record1, but actual %s.", message.Body.Items[0])
  }
}
// 运行结果
=== RUN   TestMessageBuilder
--- PASS: TestMessageBuilder (0.00s)
PASS

主页

索引

模块索引

搜索页面