创建型之建造者模式

建造者模式

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。
建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可选参数,“定制化”地创建不同的对象。
在这里插入图片描述
其实在 Golang 中对于创建类参数比较多的对象的时候,我们常见的做法是必填参数直接传递,可选参数通过传递可变的方法进行创建

builder 模式

package main

import (
	"errors"
	"fmt"
	"time"
)

type Options struct {
	ConnTimeout time.Duration
	ReadTimeout time.Duration
	RetryTime   int32
}

// 传统模式 一
// 存在问题:参数序列长,不利于维护
// 改进:如果将传入参数改为一个参数对象,可以解决参数序列过长的问题,但参数对象可能也会很庞大,进而出现其他问题
func NewOptionsV1(connTimeout, readTimeout time.Duration, RetryTime int32) (opt Options, err error) {
	opt = Options{
		ConnTimeout: connTimeout,
		ReadTimeout: readTimeout,
		RetryTime:   RetryTime,
	}

	if opt.ReadTimeout < opt.ConnTimeout {
		err = errors.New("error params")
		return
	}

	return
}

// 传统模式 二
// 存在问题:校验逻辑(必填参数、依赖关系校验)放在什么地方
func NewOptionsV2() *Options {
	return &Options{}
}

func (opt *Options) SetConnTimeout(connTimeout time.Duration) {
	opt.ConnTimeout = connTimeout
}

func (opt *Options) SetReadTimeout(readTimeout time.Duration) {
	opt.ReadTimeout = readTimeout
}

func (opt *Options) SetRetryTime(retryTime int32) {
	opt.RetryTime = retryTime
}

// builder 模式
func (opt *Options) build() (err error) {
	// 校验逻辑
	if opt.ReadTimeout < opt.ConnTimeout {
		err = errors.New("error params")
		return
	}
	return
}

func (opt *Options) setConnTimeout(timeout time.Duration) *Options {
	opt.ConnTimeout = timeout
	return opt
}

func (opt *Options) setReadTimeout(timeout time.Duration) *Options {
	opt.ReadTimeout = timeout
	return opt
}

func (opt *Options) setRetryTime(times int32) *Options {
	opt.RetryTime = times
	return opt
}

func Build() {
	opt := &Options{}
	err := opt.
		setConnTimeout(100 * time.Second).
		setReadTimeout(1000 * time.Second).
		setRetryTime(1).
		build()
	if err != nil {
		panic(err)
	}
	fmt.Printf("BuildV1: %+v\n", opt)
}

函数式选项模式:Go 当中用得更多

// 变体:Go 当中用得更多
type Opt func(option *Options)

func WithConnTimeout(connTimeout time.Duration) Opt {
	return func(option *Options) {
		option.ConnTimeout = connTimeout
	}
}

func WithRetryTime(retryTime int32) Opt {
	return func(option *Options) {
		option.RetryTime = retryTime
	}
}

func NewOptionsV3(requiredParams1 int, requiredParams2 string, opts ...Opt) *Options {
	// 里面可有默认参数
	options := &Options{}

	// 其余参数通过传进来的一个接受目标对象的函数列表来一一设置目标对象的相应属性
	for _, opt := range opts {
		opt(options)
	}

	// 校验逻辑
	return options
}

func BuildV2() {
	opt := NewOptionsV3(1, "test", WithConnTimeout(1*time.Second), WithRetryTime(2))
	fmt.Printf("BuildV2: %+v\n", opt)
}

再举一列

package builder

import "fmt"

// ResourcePoolConfigOption option
type ResourcePoolConfigOption struct {
	maxTotal int
	maxIdle  int
	minIdle  int
}

// ResourcePoolConfigOptFunc to set option
type ResourcePoolConfigOptFunc func(option *ResourcePoolConfigOption)

// NewResourcePoolConfig NewResourcePoolConfig
func NewResourcePoolConfig(name string, opts ...ResourcePoolConfigOptFunc) (*ResourcePoolConfig, error) {
	if name == "" {
		return nil, fmt.Errorf("name can not be empty")
	}

	// 目标对象可有默认值
	option := &ResourcePoolConfigOption{
		maxTotal: 10,
		maxIdle:  9,
		minIdle:  1,
	}

	// 可选参数的设置
	for _, opt := range opts {
		opt(option)
	}

	// 校验逻辑
	if option.maxTotal < 0 || option.maxIdle < 0 || option.minIdle < 0 {
		return nil, fmt.Errorf("args err, option: %v", option)
	}

	if option.maxTotal < option.maxIdle || option.minIdle > option.maxIdle {
		return nil, fmt.Errorf("args err, option: %v", option)
	}

	return &ResourcePoolConfig{
		name:     name,
		maxTotal: option.maxTotal,
		maxIdle:  option.maxIdle,
		minIdle:  option.minIdle,
	}, nil
}

应用场景

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

总结

其实可以看到,绝大多数情况下直接使用后面的这种方式就可以了,并且在编写公共库的时候,强烈建议入口的参数都可以这么传递,这样可以最大程度的保证我们公共库的兼容性,避免在后续的更新的时候出现破坏性的更新的情况。

实际工作中,db操作go语言基本都选择的gorm,此时就可以灵活使用函数式选项模式拼接db很多where条件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值