:::tips 本质上, 建造者模式是为了能灵活的配置类初始化参数
:::
前言
建造者模式也是用于类的创建的, 但是直接使用构造函数或者用set方法就能创建对象, 为什么还需要一个 建造者模式
呢? 工厂模式
也可以创建对象, 他们的区别是什么?
为什么需要建造者模式
在平时的开发中, 创建一个对象最常用的方式就是 new
或者 构造函数
完成, 那么什么时候这种方式不能用呢, 我们通过一个实例来看一下
假设我们需要一个资源池配置类 ResourcePoolConfig
,编码实现这个类
package main
import "fmt"
type CResourcePoolConfig struct {
name string
maxTotal uint
maxIdle uint
minIdle 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 string
Port int
Protocol string
Timeout time.Duration
MaxConns int
TLS *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 string
Port int
Conf *Config
}
type Config struct {
Protocol string
Timeout time.Duration
Maxconns int
TLS *tls.Config
}
func NewServer(addr string, port int, conf *Config) (*Server, error) {
//...
}
//Using the default configuratrion
srv1, _ := 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 = addr
sb.Server.Port = port
//其它代码设置其它成员的默认值
return sb
}
func (sb *ServerBuilder) WithProtocol(protocol string) *ServerBuilder {
sb.Server.Protocol = protocol
return sb
}
func (sb *ServerBuilder) WithMaxConn( maxconn int) *ServerBuilder {
sb.Server.MaxConns = maxconn
return sb
}
func (sb *ServerBuilder) WithTimeOut( timeout time.Duration) *ServerBuilder {
sb.Server.Timeout = timeout
return sb
}
func (sb *ServerBuilder) WithTLS( tls *tls.Config) *ServerBuilder {
sb.Server.TLS = tls
return 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 main
import (
"crypto/tls"
"fmt"
"time"
)
type Server struct {
Addr string
Port int
Protocol string
Timeout time.Duration
MaxConns int
TLS *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)
}
这也是目前各个开源项目中最常用的配置方式, 墙裂推荐