建造者模式 Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式。
实际上,建造者模式的原理和代码实现非常简单,掌握起来并不难,难点在于应用场景。
下面通过一些示例来看看建造者模式。
示例 现在有一个需求要创建一个网络请求对象,既然是网络请求对象那么就会有一些参数。如下表:
字段
解释
是否为必填
默认值
from
源地址
是
“”
to
目标地址
是
“”
host
本机地址
是
“”
expires
过期时间
是
0
method
sip 请求方法
是
“”
userAgent
代理名称
否
sip
body
请求体
否
“”
根据以上表格来写 Go 语言代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type Request struct { from *FromHeader to *ToHeader host string transport string expires *Expires method RequestMethod userAgent *UserAgentHeader body string } func NewRequest (from *FromHeader, to *ToHeader, expires *Expires, method RequestMethod, ua *UserAgentHeader,host,transport,body string ) *Request { r := new (Request) if from != nil { r.from = from } if to != nil { r.to = to } return r }
现在有一个 NewRequest 函数来创建 Reqeust 对象,该对象有 5 个必选项再加上 2 个可选项,该函数就有入参 7 个选项。但如果后续可配置项逐渐增多的话,按照现在的设计思路,该函数的参数列表就变得很长,代码在可读性和易用性上都会变得很差。特别是调用该函数的时候,容易搞错各参数的顺序,传递进错误的参数值,导致 bug。
那换一种思路,修改 NewRequest 函数的参数列表,只把必选参数当作入参,可选参数把字段的可导出性设置成 public。这样可以强制创建实例的时候把必要参数填写进去:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 type Request struct { from *FromHeader to *ToHeader host string transport string expires *Expires method RequestMethod UserAgent *UserAgentHeader Body string } func NewRequest (from *FromHeader, to *ToHeader, expires *Expires, method RequestMethod,host,transport string ) *Request { r := new (Request) if from != nil { r.from = from } if to != nil { r.to = to } return r }
经过一次改造之后,没有了冗长的函数调用和参数,代码在可读性和易用性提高了很多:
1 2 3 4 5 6 7 r := NewRequest() r.body = body r.userAgent = ua sendRequest(r)
普通对象创建函数的痛点 至此,我们仍然没有用到建造者模式,通过 NewRequest 函数设置必填项,通过字段的可访问性去配置可选配置项,就能实现我们的设计需求。如果我们把问题的难度再加大点,比如,还需要解决下面这三个问题,那现在的设计思路就不能满足了。
如果该对象的必选配置项有很多的话,那该函数又会出现参数列表很长的问题;
假设配置项有依赖、约束关系。比如 body 可选项不接受 JSON 文本以外的数据,而 body 字段又只能通过字段赋值的方式去修改,校验的逻辑不知道写在哪里了;
假设该对象是一个不可变对象,也就是该对象的所有字段都是在包外无法访问的,那当前方案可选字段也就无法实现这个需求了。
为了解决以上问题,建造者模式就派上用场了。
首先,我们先创建 XXBuilder 类,比如这里就是 RequestBuilder 类,并且通过 Set()方法设置建造者类的变量值,然后在使用 build()方法真正的创建对象之前,做集中的校验,校验通过之后才会返回 Request 对象。
除此之外,把之前用于创建对象的 NewRequest()方法给删除掉,这样我们想要获取到 Request 对象的话只能先创建 RequstBuilder 类后调用 build 方法才能获取 Reqeust 对象。具体的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 type RequestBuilder struct { from *FromHeader to *ToHeader host string transport string expires *Expires method RequestMethod userAgent *UserAgentHeader body string } func NewRequestBuilder () *RequestBuilder { userAgent := UserAgentHeader("GoSIP" ) rb := &RequestBuilder{ host: "localhost" , body: "" , userAgent: &userAgent, } return rb } func (rb *RequestBuilder) SetHost(host string ) *RequestBuilder { if host == "" { rb.host = "localhost" } else { rb.host = host } return rb } func (rb *RequestBuilder) SetBody(body string ) *RequestBuilder { rb.body = body return rb } func (rb *RequestBuilder) SetFrom(address *Address) *RequestBuilder { if address == nil { rb.from = nil } else { address = address.Clone() if address.Uri.Host() == "" { address.Uri.SetHost(rb.host) } rb.from = &FromHeader{ DisplayName: address.DisplayName, Address: address.Uri, Params: address.Params, } } return rb } func (rb *RequestBuilder) Build() (Request, error ) { if rb.from == nil { return nil , fmt.Errorf("empty 'From' header" ) } if rb.to == nil { return nil , fmt.Errorf("empty 'To' header" ) } hdrs := make ([]Header, 0 ) hdrs = append (hdrs, rb.cseq, rb.from, rb.to, rb.callID) if rb.expires != nil { hdrs = append (hdrs, rb.expires) } if rb.contentType != nil { hdrs = append (hdrs, rb.contentType) } if rb.accept != nil { hdrs = append (hdrs, rb.accept) } if rb.userAgent != nil { hdrs = append (hdrs, rb.userAgent) } for _, header := range rb.generic { hdrs = append (hdrs, header) } req := NewRequest("" , rb.method, rb.recipient, sipVersion, hdrs, "" , nil ) req.SetBody(rb.body, true ) return req, nil }
和工厂模式有何区别? 建造者模式是让建造者类来负责对象的创建工作。[[工厂模式]]是由工厂类来负责对象创建的工作。那它们之间有什么区别呢?
工厂模式是用来创建不同 但是相关类型的对象(继承同一父类或者接口的一组实现),由给定的参数来决定创建哪一种类型的对象。而建造者模式是用来创建一种类型的复杂对象 ,通过设置不同的可选参数,“定制化”地创建不同的对象。
网上有一个经典的例子很好地解释了两者的区别。
顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。
实际上,我们也不要太学院派,非得把工厂模式、建造者模式分得那么清楚,我们需要知道的是,每个模式为什么这么设计,能解决什么问题。
只有了解了这些最本质的东西,我们才能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的问题。