詳解Go語言如何利用高階函數(shù)寫出優(yōu)雅的代碼
前言
go項(xiàng)目中經(jīng)常需要查詢db,按照以前java開發(fā)經(jīng)驗(yàn),會根據(jù)查詢條件寫很多方法,如:
- GetUserByUserID
- GetUsersByName
- GetUsersByAge
每一種查詢條件寫一個方法,這種方式對外是挺好的,對外遵循嚴(yán)格原則,讓每個對外的方法接口是明確的。但是對內(nèi)的話,應(yīng)該盡可能的通用,做到代碼復(fù)用,少寫代碼,讓代碼看起來更優(yōu)雅、整潔。
問題
在review代碼的時候,針對上面3個方法,一般寫法是
func GetUserByUserID(ctx context.Context, userID int64) (*User, error){
db := GetDB(ctx)
var user User
if userID > 0 {
db = db.Where(`userID = ?`, userID)
}
if err := db.Model(&User{}).Find(&user).Err; err != nil {
return nil, err
}
return user, nil
}
func GetUsersByName(ctx context.Context, name string) (*User, error){
db := GetDB(ctx)
var users []User
if name != "" {
db = db.Where(`name like '%%'`, name)
}
if err := db.Model(&User{}).Find(&users).Err; err != nil {
return nil, err
}
return users, nil
}
func GetUsersByAge(ctx context.Context, age int64) (*User, error){
db := GetDB(ctx)
var user User
if age > 0 {
db = db.Where(`age = ?`, age)
}
if err := db.Model(&User{}).Find(&user).Err; err != nil {
return nil, err
}
return user, nil
}當(dāng)User表上字段有幾十個的時候,上面類似的方法會越來越多,代碼沒有做到復(fù)用。當(dāng)有Teacher表、Class表等其他表的時候,上面的查詢方法又要翻倍。
調(diào)用方也會寫的很死,參數(shù)固定。當(dāng)要增加一個查詢條件的時候,要么改原來的函數(shù),增加一個參數(shù),這樣其他調(diào)用的地方也都要改;要么新寫一個函數(shù),這樣函數(shù)越來越多,難以維護(hù)和閱讀。
上面是青銅寫法,針對這種情況,下面介紹幾種白銀、黃金、王者寫法
白銀
將入?yún)⒍x成一個結(jié)構(gòu)體
type UserParam struct {
ID int64
Name string
Age int64
}將入?yún)⒍挤旁赨serParam結(jié)構(gòu)體中
func GetUserInfo(ctx context.Context, info *UserParam) ([]*User, error) {
db := GetDB(ctx)
db = db.Model(&User{})
var infos []*User
if info.ID > 0 {
db = db.Where("user_id = ?", info.ID)
}
if info.Name != "" {
db = db.Where("user_name = ?", info.Name)
}
if info.Age > 0 {
db = db.Where("age = ?", info.Age)
}
if err := db.Find(&infos).Err; err != nil {
return nil, err
}
return infos, nil
}這個代碼寫到這里,相比最開始的方法其實(shí)已經(jīng)好了不少,至少 dao 層的方法從很多個入?yún)⒆兂闪艘粋€,調(diào)用方的代碼也可以根據(jù)自己的需要構(gòu)建參數(shù),不需要很多空占位符。但是存在的問題也比較明顯:仍然有很多判空不說,還引入了一個多余的結(jié)構(gòu)體。如果我們就到此結(jié)束的話,多少有點(diǎn)遺憾。
另外,如果我們再擴(kuò)展一下業(yè)務(wù)場景,我們使用的不是等值查詢,而是多值查詢或者區(qū)間查詢,比如查詢 status in (a, b),那上面的代碼又怎么擴(kuò)展呢?是不是又要引入一個方法,方法繁瑣暫且不說,方法名叫啥都會讓我們糾結(jié)很久;或許可以嘗試把每個參數(shù)都從單值擴(kuò)展成數(shù)組,然后賦值的地方從 = 改為 in()的方式,所有參數(shù)查詢都使用 in 顯然對性能不是那么友好。
黃金
更高級的優(yōu)化方法,是使用高階函數(shù)。
type Option func(*gorm.DB)
定義 Option 是一個函數(shù),這個函數(shù)的入?yún)㈩愋褪?gorm.DB,返回值為空。
然后針對每一個需要查詢的字段,定義一個高階函數(shù)
func UserID(ID int64) Option {
return func(db *gorm.DB) {
db.Where("`id` = ?", ID)
}
}
func Name(name int64) Option {
return func(db *gorm.DB) {
db.Where("`name` like %?%", name)
}
}
func Age(age int64) Option {
return func(db *gorm.DB) {
db.Where("`age` = ?", age)
}
}返回值是Option類型。
這樣上面3個方法就可以合并成一個方法了
func GetUsersByCondition(ctx context.Context, opts ...Option)([]*User, error) {
db := GetDB(ctx)
for i:=range opts {
opts[i](db)
}
var users []User
if err := db.Model(&User{}).Find(&users).Err; err != nil {
return nil, err
}
return users, nil
}沒有對比就沒有傷害,通過和最開始的方法比較,可以看到方法的入?yún)?strong>由多個不同類型的參數(shù)變成了一組相同類型的函數(shù),因此在處理這些參數(shù)的時候,也無需一個一個的判空,而是直接使用一個 for 循環(huán)就搞定,相比之前已經(jīng)簡潔了很多。
還可以擴(kuò)展其他查詢條件,比如IN,大于等
func UserIDs(IDs int64) Option {
return func(db *gorm.DB) {
db.Where("`id` in (?)", IDs)
}
}
func AgeGT(age int64) Option {
return func(db *gorm.DB) {
db.Where("`age` > ?", age)
}
}而且這個查詢條件最終是轉(zhuǎn)換成Where條件,跟具體的表無關(guān),也就是說這些定義是可以被其他表復(fù)用的。
王者
優(yōu)化到上述方法已經(jīng)可以了,但是王者一般會繼續(xù)優(yōu)化。
上述方法GetUsersByCondition只能查User表,能不能更通用一些,查任意表呢?分享GetUsersByCondition方法,發(fā)現(xiàn)如果要做到查任意表,有2個阻礙:
- 表明是在方法中寫死的
- 返回值定義的是[]*User,不能通用
針對第一個問題,我們可以定義一個Option來實(shí)現(xiàn)
func TableName(tableName string) Option {
return func(db *grom.DB) {
db.Table(tableName)
}
}針對第二個問題,可以將返回參數(shù)作為入?yún)ⅲㄟ^引用的方式傳進(jìn)來
func GetRecords(ctx context.Context, in any, opts ...Option) {
db := GetDB(ctx)
for i:=range opts {
opts[i](db)
}
return db.Find(in).Err
}
// 調(diào)用:根據(jù)user name 和age 查詢users
var users []User
if err := GetRecords(ctx, &users, TableName("user"), Name("張三"), Age(18)); err != nil {
// TODO
}總結(jié)
這里通過對 grom 查詢條件的抽象,大大簡化了對 DB 組合查詢的寫法,提升了代碼的簡潔。
以上就是詳解Go語言如何利用高階函數(shù)寫出優(yōu)雅的代碼的詳細(xì)內(nèi)容,更多關(guān)于Go語言高階函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
利用GO語言實(shí)現(xiàn)多人聊天室實(shí)例教程
聊天室的實(shí)現(xiàn)大家應(yīng)該都遇到過,這篇文章主要給大家介紹了關(guān)于利用GO語言實(shí)現(xiàn)多人聊天室的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起看看吧。2018-03-03
基于Go+OpenCV實(shí)現(xiàn)人臉識別功能的詳細(xì)示例
OpenCV是一個強(qiáng)大的計(jì)算機(jī)視覺庫,提供了豐富的圖像處理和計(jì)算機(jī)視覺算法,本文將向你介紹在Mac上安裝OpenCV的步驟,并演示如何使用Go的OpenCV綁定庫進(jìn)行人臉識別,需要的朋友可以參考下2023-07-07
使用Go語言封裝實(shí)現(xiàn)郵件發(fā)送功能
在現(xiàn)代 Web 開發(fā)中,郵件發(fā)送功能是一個常見的需求,本文將介紹如何在 Go 語言中封裝一個通用的郵件發(fā)送包,支持驗(yàn)證碼發(fā)送和通用郵件發(fā)送,需要的可以參考下2025-03-03
Go Mongox輕松實(shí)現(xiàn)MongoDB的時間字段自動填充
這篇文章主要為大家詳細(xì)介紹了Go語言如何使用 mongox 庫,在插入和更新數(shù)據(jù)時自動填充時間字段,從而提升開發(fā)效率并減少重復(fù)代碼,需要的可以參考下2025-02-02
Go?Ticker?周期性定時器用法及實(shí)現(xiàn)原理詳解
這篇文章主要為大家介紹了Go?Ticker?周期性定時器用法及實(shí)現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08

