Preface
In go projects, db is often needed. According to previous Java development experience, many methods will be written according to query conditions, such as:
- GetUserByUserID
- GetUsersByName
- GetUsersByAge
Write a method for each query condition. This method is good for the outside world. It follows strict principles to the outside world, so that the interface of each external method is clear. But if it is internal, it should be as general as possible to reuse the code and write less code to make the code look more elegant and tidy.
question
When reviewing the code, the general writing method is
func GetUserByUserID(ctx , userID int64) (*User, error){ db := GetDB(ctx) var user User if userID > 0 { db = (`userID = ?`, userID) } if err := (&User{}).Find(&user).Err; err != nil { return nil, err } return user, nil } func GetUsersByName(ctx , name string) (*User, error){ db := GetDB(ctx) var users []User if name != "" { db = (`name like '%%'`, name) } if err := (&User{}).Find(&users).Err; err != nil { return nil, err } return users, nil } func GetUsersByAge(ctx , age int64) (*User, error){ db := GetDB(ctx) var user User if age > 0 { db = (`age = ?`, age) } if err := (&User{}).Find(&user).Err; err != nil { return nil, err } return user, nil }
When there are dozens of fields on the User table, there will be more and more similar methods above, and the code will not be reused. When there are other tables such as Teacher table, Class table, etc., the above query method must be doubled.
The caller will also write very deadly, and the parameters are fixed. When adding a query condition, either change the original function and add a parameter, so that other calls must be changed; or write a new function, so that more and more functions will be difficult to maintain and read.
The above is the bronze writing method. In this case, the following are several ways to write silver, gold and kings.
silver
Define the incoming parameter into a structure
type UserParam struct { ID int64 Name string Age int64 }
Put all the parameters in the UserParam structure
func GetUserInfo(ctx , info *UserParam) ([]*User, error) { db := GetDB(ctx) db = (&User{}) var infos []*User if > 0 { db = ("user_id = ?", ) } if != "" { db = ("user_name = ?", ) } if > 0 { db = ("age = ?", ) } if err := (&infos).Err; err != nil { return nil, err } return infos, nil }
This code has been written here, and it is actually much better than the initial method, at least the dao layer methodFrom many entry ginseng to one, the caller's code can also beBuild parameters according to your needs, no need for many empty placeholders. But the existing problems are also quite obvious: there are still many empty words, not only are they introducedExtra structure. If we end here, it would be a bit regretful.
In addition, if we expand the business scenario, we are not using equivalent queries, butMulti-value query or interval query, for example, query status in (a, b), how can the above code be extended? Is it necessary to introduce another method? Not to mention the cumbersome method, what the method name will make us tangled for a long time; maybe we can try to expand each parameter from a single value to an array, and then change the assignment from = to in(). All parameter queries are used in. Obviously, it is not so performance-friendly.
gold
A more advanced optimization method is to use higher-order functions.
type Option func(*)
Definition Option is a function whose entry parameter type is * and the return value is empty.
Then, for each field that needs to be queried, a higher-order function is defined
func UserID(ID int64) Option { return func(db *) { ("`id` = ?", ID) } } func Name(name int64) Option { return func(db *) { ("`name` like %?%", name) } } func Age(age int64) Option { return func(db *) { ("`age` = ?", age) } }
The return value is Option type.
In this way, the above three methods can be combined into one method
func GetUsersByCondition(ctx , opts ...Option)([]*User, error) { db := GetDB(ctx) for i:=range opts { opts[i](db) } var users []User if err := (&User{}).Find(&users).Err; err != nil { return nil, err } return users, nil }
There is no harm without comparison. By comparing with the initial method, you can see the entry parameters of the method.From multiple different types of parameters to a set of functions of the same type, so when processing these parameters, there is no need to judge each one by one, but just use a for loop to get it done, which is much simpler than before.
You can also extend other query conditions, such as IN, greater than, etc.
func UserIDs(IDs int64) Option { return func(db *) { ("`id` in (?)", IDs) } } func AgeGT(age int64) Option { return func(db *) { ("`age` > ?", age) } }
Moreover, this query condition is ultimately converted to Where condition, which has nothing to do with the specific table, that is, these definitions can be multiplexed by other tables.
The King
Optimizing to the above method is already OK, but King will generally continue to optimize.
The above method GetUsersByCondition can only check the User table. Can it be more general and check any table? Share the GetUsersByCondition method and found that if you want to check any table, there are 2 obstacles:
- It shows that it was written dead in the method
- The return value defines []*User, which cannot be generalized
For the first problem, we can define an Option to implement it.
func TableName(tableName string) Option { return func(db *) { (tableName) } }
In response to the second question, you can use the return parameter as an entry parameter and pass it in through reference.
func GetRecords(ctx , in any, opts ...Option) { db := GetDB(ctx) for i:=range opts { opts[i](db) } return (in).Err } // Call: Query users based on user name and agevar users []User if err := GetRecords(ctx, &users, TableName("user"), Name("Zhang San"), Age(18)); err != nil { // TODO }
Summarize
Here, through the abstraction of grom query conditions, the writing of DB combination query is greatly simplified and the simplicity of the code is improved.
The above is a detailed explanation of how Go uses advanced functions to write elegant code. For more information about advanced functions in Go, please pay attention to my other related articles!