:::tips 本质上, 建造者模式是为了能灵活的配置类初始化参数
:::
前言
建造者模式也是用于类的创建的, 但是直接使用构造函数或者用set方法就能创建对象, 为什么还需要一个 建造者模式 呢? 工厂模式 也可以创建对象, 他们的区别是什么?
为什么需要建造者模式
在平时的开发中, 创建一个对象最常用的方式就是 new 或者 构造函数 完成, 那么什么时候这种方式不能用呢, 我们通过一个实例来看一下
假设我们需要一个资源池配置类 ResourcePoolConfig ,编码实现这个类

package mainimport "fmt"type CResourcePoolConfig struct {name stringmaxTotal uintmaxIdle uintminIdle uint}func CreatResourcePool(name string) *CResourcePoolConfig {return &CResourcePoolConfig{name: name,maxTotal: 8,maxIdle: 8,minIdle: 0,}}func (c *CResourcePoolConfig) setMaxTotal (maxTotal uint){if maxTotal > 100{return}c.maxTotal = maxTotal}func (c *CResourcePoolConfig) setMaxIdle (maxIdle uint){if maxIdle > 100{return}c.maxIdle = maxIdle}func (c *CResourcePoolConfig) setMinIdle (minIdle uint){if minIdle < 1{return}c.minIdle = minIdle}func main() {test := CreatResourcePool("a")test.setMaxTotal(70)test.setMaxIdle(50)test.setMinIdle(5)fmt.Println(test)}
- 没有遵守的原则
开闭原则如果增加了参数, 构造函数必然需要修改, 并且有可能修改入参, 这样导出都要修改其构造函数
- 随着难度加大, 上面的模式面临着以下困难
- 如果像
name一样的字段很多, 那么构造函数中的设置必然就有很多, 参数列表就会很长, 但如果我们用set来设置, 又无法放下校验的逻辑 - 假如
配置间有依赖关系,那么配置间的依赖关系或约束条件也没办法放下 - 如果我们希望配置好就不能动这个类了, 那么暴露set方法就不合适
- 如果像
golang
假设我们需要配置一下下面的业务实体
type Server struct {Addr stringPort intProtocol stringTimeout time.DurationMaxConns intTLS *tls.Config}
- Addr 地址 必填
- Port 端口号 必填
- Protocol 协议 有默认值
- Timeout 超时时间 有默认值
- MaxConns 最大连接数 有默认值
- TLS https相关证书 可以没有
由于go中没有重载, 所以可能写出下面这样的代码, 相当的不优雅, 不能忍
func NewDefaultServer(addr string, port int) (*Server, error) {return &Server{addr, port, "tcp", 30 * time.Second, 100, nil}, nil}func NewTLSServer(addr string, port int, tls *tls.Config) (*Server, error) {return &Server{addr, port, "tcp", 30 * time.Second, 100, tls}, nil}func NewServerWithTimeout(addr string, port int, timeout time.Duration) (*Server, error) {return &Server{addr, port, "tcp", timeout, 100, nil}, nil}func NewTLSServerWithMaxConnAndTimeout(addr string, port int, maxconns int, timeout time.Duration, tls *tls.Config) (*Server, error) {return &Server{addr, port, "tcp", 30 * time.Second, maxconns, tls}, nil}
配置对象的方式
type Server struct {Addr stringPort intConf *Config}type Config struct {Protocol stringTimeout time.DurationMaxconns intTLS *tls.Config}func NewServer(addr string, port int, conf *Config) (*Server, error) {//...}//Using the default configuratrionsrv1, _ := NewServer("localhost", 9000, nil)conf := ServerConfig{Protocol:"tcp", Timeout: 60*time.Duration}srv2, _ := NewServer("locahost", 9000, &conf)
将 必填 和 非必填的参数分开, 但是 Config中依然要判断 nil 感觉代码不太干净
建造者模式
//使用一个builder类来做包装type ServerBuilder struct {Server}func (sb *ServerBuilder) Create(addr string, port int) *ServerBuilder {sb.Server.Addr = addrsb.Server.Port = port//其它代码设置其它成员的默认值return sb}func (sb *ServerBuilder) WithProtocol(protocol string) *ServerBuilder {sb.Server.Protocol = protocolreturn sb}func (sb *ServerBuilder) WithMaxConn( maxconn int) *ServerBuilder {sb.Server.MaxConns = maxconnreturn sb}func (sb *ServerBuilder) WithTimeOut( timeout time.Duration) *ServerBuilder {sb.Server.Timeout = timeoutreturn sb}func (sb *ServerBuilder) WithTLS( tls *tls.Config) *ServerBuilder {sb.Server.TLS = tlsreturn sb}func (sb *ServerBuilder) Build() (Server) {return sb.Server}func main(){sb := ServerBuilder{}server, err := sb.Create("127.0.0.1", 8080).WithProtocol("udp").WithMaxConn(1024).WithTimeOut(30*time.Second).Build()}
- 这就是标准的一种建造者模式了, 不过这种方式在
错误处理的时候会比较麻烦 - 另外随意暴露了 set 方法, 有可能导致某处被更改
函数式实现(Functional Options)
package mainimport ("crypto/tls""fmt""time")type Server struct {Addr stringPort intProtocol stringTimeout time.DurationMaxConns intTLS *tls.Config}type Option func(*Server)func WithProtocol(p string) Option{return func(s *Server) {s.Protocol = p}}func WithTimeOut(t time.Duration) Option{return func(s *Server) {s.Timeout = t}}func WithMaxConns(maxconns int) Option{return func(s *Server) {s.MaxConns = maxconns}}func WithTLS(tls *tls.Config) Option{return func(s *Server) {s.TLS = tls}}func NewServer(addr string, port int , options... Option) (*Server, error){srv := Server{Addr : addr,Port: port,Protocol: "tcp",Timeout: 30 * time.Second,MaxConns: 1000,TLS : nil,}for _, option := range options{option(&srv)}return &srv, nil}func main() {s1 , _ := NewServer("127.0.0.1", 1024)s2 , _ := NewServer("127.0.0.1", 1024, WithProtocol("kcp"))s3 , _ := NewServer("0.0.0.1", 8080, WithTimeOut(1 * time.Minute), WithMaxConns(200))fmt.Println(s1,s2,s3)}
这也是目前各个开源项目中最常用的配置方式, 墙裂推荐
