Go(八)还不知道函数式选项模式?

700 阅读5分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

作者:lomtom

个人网站:lomtom.cn

个人公众号:博思奥园

你的支持就是我最大的动力。

Go系列:

  1. Go(一)基础入门
  2. Go(二)结构体
  3. Go(三)Go配置文件
  4. Go(四)Redis操作
  5. Go(五)Go不知道怎么用Gorm?
  6. Go(六)来来来,教你怎么远程调用
  7. Go(七)你说你不会并发?
  8. Go(八)还不知道函数式选项模式?

引入

option 结构体进行初始化,因为其是私有的,即只能包内访问,所以需要编写一个构造函数。

type option struct {
	A string
	B string
	C int
}

构造函数

func newOption(a, b string, c int) *option {
	return &option{
		A: a,
		B: b,
		C: c,
	}
}

使用的时候,直接调用该方法即可。

option := newOption("a","b",10)

这样的代码看起来是没有问题的,并且使用起来也是非常方便的。

但是,如果此时需求发生变化,我不再需要三个参数的option,我只需要两个或者一个参数的option,怎么办呢?

而恰好Go语言不支持方法的重载,如果需要为一个结构体写多个初始化的构造函数,相同的方法名,不同的参数,这样的情况是不允许的,但往往这样情况的使用场景还是挺多的。

那么怎么办呢,只能在编写一个构造方法,并且命名为newOption1

func newOption1(a, b string) *option {
	return &option{
		A: a,
		B: b,
	}
}

这样一看,确实没问题,也满足了我们的需求,但是谁也不敢保证日后需求不再发生变动。

那么选项模式的优势就来了。

选项模式进行初始化

选项模式利用闭包以及不定参数来达到参数的可选。

type OptionFunc func(*option)

func WithA(a string) OptionFunc {
	return func(o *option) {
		o.A = a
	}
}

func WithB(b string) OptionFunc {
	return func(o *option) {
		o.B = b
	}
}

func WithC(c int) OptionFunc {
	return func(o *option) {
		o.C = c
	}
}

首先自定义一个函数类型,函数的参数是*option,并且为三个参数编写该选项方法。

func newOption(opts... OptionFunc)(opt  *option) {
	opt = &option{
		A: "A",
		B: "B",
		C: 100,
	}
	for _, optFun := range opts {
		optFun(opt)
	}
	return
}

修改其构造方法,这样将每一个参数都作为一个选项进行初始化,如果没有传入该参数的选项,即保留默认值。

func TestGo7(t *testing.T) {
	o := newOption()
	fmt.Println(o)

	o1 := newOption(
		WithA("1"),
	)
	fmt.Println(o1)

	o2 := newOption(
		WithA("a"),
		WithB("b"),
		WithC(10),
		)
	fmt.Println(o2)
}

使用上,对结构体进行初始化时,可以选择初始化其一部分参数。

选项模式在Gorm中的应用

选项模式除了在结构体的初始化,还可以应用到哪方面呢?

恰好,对于数据库的查询操作往往也适合,在查询时,我们可能需要多个条件进行筛选,如果每种情况都写一个查询方法,不仅会使代码看起来很凌乱,并且写得多了,自己逻辑叶会变得很凌乱。

例如,这有一个字典表,拥有很多属性,如果对每个属性都需要进行筛选,那么情况就会有很多种,而利用选项模式只需要对每个字段编写一个选项即可完成多条件筛选。

// DataDictionary [...]
type DataDictionary struct {
	ID         string    `gorm:"primaryKey;column:id;type:varchar(255);not null" json:"-"`
	DataType   string    `gorm:"column:data_type;type:varchar(255);not null" json:"dataType"`      // 数据类型
	DataKey    string    `gorm:"column:data_key;type:varchar(255);not null" json:"dataKey"`        // 数据key
	DataValue  string    `gorm:"column:data_value;type:varchar(255);not null" json:"dataValue"`    // 数据value
	SortNumber int       `gorm:"column:sort_number;type:int;not null" json:"sortNumber"`           // 排序编号
	Level      string    `gorm:"column:level;type:varchar(255);not null" json:"level"`             // 级别
	Deleted    bool      `gorm:"column:deleted;type:tinyint(1);not null;default:0" json:"deleted"` // 是否删除
	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null" json:"updateTime"`     // 更新时间
	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null" json:"createTime"`     // 创建时间
	DomainID   string    `gorm:"column:domain_id;type:varchar(255)" json:"domainId"`               // 主账号
}

// TableName get sql table name.获取数据库表名
func (m *DataDictionary) TableName() string {
	return "data_dictionary"
}

编写自定义方法,编写options结构体用于存储选项关系。

type options struct {
	query map[string]interface{}
}

type Option interface {
	apply(*options)
}

type optionFunc func(*options)

func (f optionFunc) apply(o *options) {
	f(o)
}

DataType字段和DataKey字段编写查询选项方法。

/****************************************  选项模式  ***********************************************************/

// WithDataType dataType获取 字典类别
func (obj *DataDictionaryMapper) WithDataType(dataType string) Option {
	return optionFunc(func(o *options) { o.query["data_type"] = dataType })
}

// WithDataKey dataType获取 字典类别
func (obj *DataDictionaryMapper) WithDataKey(dataKey string) Option {
	return optionFunc(func(o *options) { o.query["data_key"] = dataKey })
}

编写查询方法,并且将选项遍历放入最开始定义的结构体options中。

// GetByOption 功能选项模式获取
func (obj *DataDictionaryMapper) GetByOption(opts ...Option) (result []po.DataDictionary, err error) {
	options := options{
		query: make(map[string]interface{}, len(opts)),
	}
	for _, o := range opts {
		o.apply(&options)
	}
	err = obj.DB.Where(options.query).Find(&result).Error
	return
}

使用上直接将选项作为参数传入即可

res, _:= c.GetByOption(c.WithDataType("position"))

这里你可能会纳闷,直接将options.query传入where为什么可以?

上面我们定义了options.querymap[string]interface{},我们点进Where方法源码中的tx.Statement.BuildCondition(query, args...)就可以看到,gorm会对参数进行类型判断,并且转换为sql

switch v := arg.(type) {
		case clause.Expression:
			conds = append(conds, v)
		case *DB:
			if cs, ok := v.Statement.Clauses["WHERE"]; ok {
				if where, ok := cs.Expression.(clause.Where); ok {
					if len(where.Exprs) == 1 {
						if orConds, ok := where.Exprs[0].(clause.OrConditions); ok {
							where.Exprs[0] = clause.AndConditions(orConds)
						}
					}
					conds = append(conds, clause.And(where.Exprs...))
				} else if cs.Expression != nil {
					conds = append(conds, cs.Expression)
				}
			}
		case map[interface{}]interface{}:
			for i, j := range v {
				conds = append(conds, clause.Eq{Column: i, Value: j})
			}
		case map[string]string:
			var keys = make([]string, 0, len(v))
			for i := range v {
				keys = append(keys, i)
			}
			sort.Strings(keys)

			for _, key := range keys {
				conds = append(conds, clause.Eq{Column: key, Value: v[key]})
			}
		case map[string]interface{}:
			var keys = make([]string, 0, len(v))
			for i := range v {
				keys = append(keys, i)
			}
			sort.Strings(keys)

			for _, key := range keys {
				reflectValue := reflect.Indirect(reflect.ValueOf(v[key]))
				switch reflectValue.Kind() {
				case reflect.Slice, reflect.Array:
					if _, ok := v[key].(driver.Valuer); ok {
						conds = append(conds, clause.Eq{Column: key, Value: v[key]})
					} else if _, ok := v[key].(Valuer); ok {
						conds = append(conds, clause.Eq{Column: key, Value: v[key]})
					} else {
						// optimize reflect value length
						valueLen := reflectValue.Len()
						values := make([]interface{}, valueLen)
						for i := 0; i < valueLen; i++ {
							values[i] = reflectValue.Index(i).Interface()
						}

						conds = append(conds, clause.IN{Column: key, Values: values})
					}
				default:
					conds = append(conds, clause.Eq{Column: key, Value: v[key]})
				}
			}

参考: golang中函数类型 Go语言设计模式之函数式选项模式 Functional Options Pattern in Go 选项模式(option)