日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網(wǎng)站首頁 編程語言 正文

詳解Go語言如何利用高階函數(shù)寫出優(yōu)雅的代碼_Golang

作者:nil ? 更新時間: 2023-02-14 編程語言

前言

go項目中經(jīng)常需要查詢db,按照以前java開發(fā)經(jīng)驗,會根據(jù)查詢條件寫很多方法,如:

  • GetUserByUserID
  • GetUsersByName
  • GetUsersByAge

每一種查詢條件寫一個方法,這種方式對外是挺好的,對外遵循嚴格原則,讓每個對外的方法接口是明確的。但是對內的話,應該盡可能的通用,做到代碼復用,少寫代碼,讓代碼看起來更優(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
}

當User表上字段有幾十個的時候,上面類似的方法會越來越多,代碼沒有做到復用。當有Teacher表、Class表等其他表的時候,上面的查詢方法又要翻倍。

調用方也會寫的很死,參數(shù)固定。當要增加一個查詢條件的時候,要么改原來的函數(shù),增加一個參數(shù),這樣其他調用的地方也都要改;要么新寫一個函數(shù),這樣函數(shù)越來越多,難以維護和閱讀。

上面是青銅寫法,針對這種情況,下面介紹幾種白銀、黃金、王者寫法

白銀

將入?yún)⒍x成一個結構體

type UserParam struct {
    ID int64
    Name string
    Age int64
}

將入?yún)⒍挤旁赨serParam結構體中

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
}

這個代碼寫到這里,相比最開始的方法其實已經(jīng)好了不少,至少 dao 層的方法從很多個入?yún)⒆兂闪艘粋€,調用方的代碼也可以根據(jù)自己的需要構建參數(shù),不需要很多空占位符。但是存在的問題也比較明顯:仍然有很多判空不說,還引入了一個多余的結構體。如果我們就到此結束的話,多少有點遺憾。

另外,如果我們再擴展一下業(yè)務場景,我們使用的不是等值查詢,而是多值查詢或者區(qū)間查詢,比如查詢 status in (a, b),那上面的代碼又怎么擴展呢?是不是又要引入一個方法,方法繁瑣暫且不說,方法名叫啥都會讓我們糾結很久;或許可以嘗試把每個參數(shù)都從單值擴展成數(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)簡潔了很多。

還可以擴展其他查詢條件,比如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)    
    } 
}

而且這個查詢條件最終是轉換成Where條件,跟具體的表無關,也就是說這些定義是可以被其他表復用的。

王者

優(yōu)化到上述方法已經(jīng)可以了,但是王者一般會繼續(xù)優(yōu)化。

上述方法GetUsersByCondition只能查User表,能不能更通用一些,查任意表呢?分享GetUsersByCondition方法,發(fā)現(xiàn)如果要做到查任意表,有2個阻礙:

  • 表明是在方法中寫死的
  • 返回值定義的是[]*User,不能通用

針對第一個問題,我們可以定義一個Option來實現(xiàn)

func TableName(tableName string) Option {
    return func(db *grom.DB) {
        db.Table(tableName)
    }
}

針對第二個問題,可以將返回參數(shù)作為入?yú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
}

// 調用:根據(jù)user name 和age 查詢users
var users []User
if err := GetRecords(ctx, &users, TableName("user"), Name("張三"), Age(18)); err != nil {
    // TODO
}

總結

這里通過對 grom 查詢條件的抽象,大大簡化了對 DB 組合查詢的寫法,提升了代碼的簡潔。

原文鏈接:https://juejin.cn/post/7184804445991993403

欄目分類
最近更新