This is the 13th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021
Author: lomtom
Personal website: lomtom.cn
Personal public account: Bosiao Yuan
Your support is my biggest motivation.
The Go series:
- Go (1) Basic introduction
- Go (ii) structure
- Go (3) Go configuration file
- Go (4) Redis operation
- Go (5) Go do not know how to use Gorm?
- Go (6) come come come, teach you how to call remotely
- You say you can’t do concurrency?
- Go (8) still don’t know the functional option mode?
The introduction of
Initialize the Option structure because it is private, meaning it can only be accessed inside the package, so you need to write a constructor.
type option struct {
A string
B string
C int
}
Copy the code
The constructor
func newOption(a, b string, c int) *option {
return &option{
A: a,
B: b,
C: c,
}
}
Copy the code
When used, simply call this method.
option := newOption("a"."b".10)
Copy the code
Such code appears to be problem-free, and is very convenient to use.
But what if the requirements change and I no longer need the option with three parameters, I only need the option with two or one parameters?
The Go language does not support method overloading. If you need to write multiple initializing constructors for a structure, with the same method name and different parameters, this situation is not allowed, but often this situation is used in many scenarios.
So what do you do? You have to write only one constructor and name it newOption1.
func newOption1(a, b string) *option {
return &option{
A: a,
B: b,
}
}
Copy the code
At first glance, it is ok and meets our needs, but there is no guarantee that demand will not change in the future.
So here’s the advantage of option mode.
Option mode for initialization
The option pattern uses closures and undetermined arguments to achieve parameter selectibility.
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
}
}
Copy the code
Start by defining a function type that takes *option and writing the option method for three arguments.
func newOption(opts... OptionFunc)(opt *option) {
opt = &option{
A: "A",
B: "B",
C: 100,}for _, optFun := range opts {
optFun(opt)
}
return
}
Copy the code
Modify its constructor so that each parameter is initialized as an option, leaving the default if no option for that parameter is passed in.
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)
}
Copy the code
In use, when initializing a structure, you can choose to initialize some of its parameters.
Application of option pattern in Gorm
What else can the option pattern be used for besides struct initialization?
As it happens, the database query operation is often also suitable, in the query, we may need to filter multiple conditions, if each case to write a query method, not only will make the code look messy, and write too much, their logic leaf will become messy.
For example, if you have a dictionary table with many attributes and you need to filter for each attribute, you can have many different situations, whereas with the option mode you only need to write one option for each field to do multi-conditional 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"` / / the key data
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"` // Whether to 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"` // Create time
DomainID string `gorm:"column:domain_id; type:varchar(255)" json:"domainId"` / / the main account
}
// TableName get sql table name. Gets the database table name
func (m *DataDictionary) TableName(a) string {
return "data_dictionary"
}
Copy the code
Write custom methods and write an Options structure 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)
}
Copy the code
Write query option methods on DataType and DataKey fields.
/ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * option model * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * /
// WithDataType dataType gets the dictionary category
func (obj *DataDictionaryMapper) WithDataType(dataType string) Option {
return optionFunc(func(o *options) { o.query["data_type"] = dataType })
}
// WithDataKey dataType gets the dictionary category
func (obj *DataDictionaryMapper) WithDataKey(dataKey string) Option {
return optionFunc(func(o *options) { o.query["data_key"] = dataKey })
}
Copy the code
Write the query method and iterate through the options in the structure you originally defined, Options.
// GetByOption Obtains the function option mode
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
}
Copy the code
Pass the option as a parameter
res, _:= c.GetByOption(c.WithDataType("position"))
Copy the code
You may be wondering why it is ok to pass options.query directly to where.
We define the above options. Query for map [string] interface {}, tx. In our point into the Where method source Statement. BuildCondition (query, args…). As you can see, GORM will type the 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 ifcs.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]})
}
}
Copy the code
Functional Options Pattern in Go (option)