Author: lomtom
Personal website: lomtom.cn
Official account No. Bosio Park
Your support is my biggest motivation.
Go series:
- Go (I) basic introduction
- Go (II) structure
- Go (III) go configuration file
- Go (IV) Redis operation
- Go (V) go doesn't know how to use Gorm?
- Go (VI) to teach you how to call remotely
- Go (VII) you said you wouldn't be concurrent?
- 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