Go doesn't know the functional option mode yet?

Author: lomtom

Personal website: lomtom.cn

Official account No. Bosio Park

Your support is my biggest motivation.

Go series:

  1. Go (I) basic introduction
  2. Go (II) structure
  3. Go (III) go configuration file
  4. Go (IV) Redis operation
  5. Go (V) go doesn't know how to use Gorm?
  6. Go (VI) to teach you how to call remotely
  7. Go (VII) you said you wouldn't be concurrent?
  8. Go (8) doesn't know the functional option mode yet?

introduce

Initialize the option structure, because it is private, that is, it can only be accessed within the package, so you need to write a constructor.

type option struct {
	A string
	B string
	C int
}

Constructor

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

When using, you can call this method directly.

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

Such code seems to be no problem, and it is very convenient to use.

However, if the requirements change at this time, I no longer need three parameter options, but only two or one parameter options. What should I do?

The Go language just doesn't support method overloading. If you need to write multiple initialized constructors for a structure, the same method name and different parameters are not allowed, but there are often many usage scenarios in this case.

So what to do? You can only write a constructor and name it newOption1.

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

In this way, it is really no problem and meets our needs, but no one can guarantee that there will be no change in demand in the future.

Then the advantage of option mode comes.

Option mode for initialization

Option mode uses closures and indefinite parameters to achieve optional parameters.

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
	}
}

First, customize a function type. The parameter of the function is * option, and write the option method for the three parameters.

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

Modify its construction method so that each parameter is initialized as an option. If there is no option passed in to the parameter, the default value is retained.

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)
}

When initializing a structure, you can choose to initialize some of its parameters.

Application of option mode in Gorm

In addition to initializing the structure, what else can the option mode be applied to?

As it happens, the query operation for the database is also suitable. During the query, we may need multiple criteria to filter. If we write a query method in each case, the code will not only look messy, but also write too much, and our logic leaves will become messy.

For example, there is a dictionary table with many attributes. If you need to filter each attribute, there will be many situations. Using the option mode, you only need to write one option for each field to complete multi condition filtering.

// 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"`      // data type
	DataKey    string    `gorm:"column:data_key;type:varchar(255);not null" json:"dataKey"`        // Data key
	DataValue  string    `gorm:"column:data_value;type:varchar(255);not null" json:"dataValue"`    // Data value
	SortNumber int       `gorm:"column:sort_number;type:int;not null" json:"sortNumber"`           // Sort number
	Level      string    `gorm:"column:level;type:varchar(255);not null" json:"level"`             // level
	Deleted    bool      `gorm:"column:deleted;type:tinyint(1);not null;default:0" json:"deleted"` // Delete
	UpdateTime time.Time `gorm:"column:update_time;type:timestamp;not null" json:"updateTime"`     // Update time
	CreateTime time.Time `gorm:"column:create_time;type:timestamp;not null" json:"createTime"`     // Creation time
	DomainID   string    `gorm:"column:domain_id;type:varchar(255)" json:"domainId"`               // Master account
}

// TableName get sql table name. Get database table name
func (m *DataDictionary) TableName() string {
	return "data_dictionary"
}

Write custom methods and options structs to store option relationships.

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

type Option interface {
	apply(*options)
}

type optionFunc func(*options)

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

Write query option methods for DataType and DataKey fields.

/****************************************  Option mode***********************************************************/

// Withdatatype get dictionary category
func (obj *DataDictionaryMapper) WithDataType(dataType string) Option {
	return optionFunc(func(o *options) { o.query["data_type"] = dataType })
}

// WithDataKey dataType get dictionary category
func (obj *DataDictionaryMapper) WithDataKey(dataKey string) Option {
	return optionFunc(func(o *options) { o.query["data_key"] = dataKey })
}

Write the query method and traverse the options into the structural options defined at the beginning.

// GetByOption function option mode acquisition
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
}

You can directly pass in options as parameters

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

Here you may wonder why you can directly pass options.query into where?

Above, we defined options.query as map[string]interface {}. We click tx.Statement.BuildCondition(query, args...) in the source code of Where method to see that gorm will judge the type of parameters and convert them to 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]})
				}
			}

reference resources:
Function types in golang
Functional option mode of Go language design mode
Functional Options Pattern in Go
Option mode

Tags: node.js Go Database Vue.js

Posted on Mon, 29 Nov 2021 10:21:39 -0500 by lauthiamkok