日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務,提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

Go 泛型定了,有哪些好的使用場景,哪些不好的應用場景,亦或哪些使用看起來丑?本文聊聊這個問題。

01 簡介

泛型很棒,而且 Go 變得比以前更方便了。但是與可能非常有用的 channel 類似,我們不應該僅僅因為它們存在就到處使用它們。

除了用于數(shù)據(jù)結構,泛型還有其他很好的應用場景。當然,也有一些不好的用例,比如泛型日志器。還有一些可以使用的解決方案,但相當丑陋,還有一些東西真的很丑。

讓我們分別看一個例子!

02 好的應用場景

我真正夢想在 Go 中做的以及我認為我現(xiàn)在終于可以做的是 CRUD 端點的泛型提供程序:

type Model interface {
    ID() string
}

type DataProvider[MODEL Model] interface {
    FindByID(id string) (MODEL, error)
    List() ([]MODEL, error)
    Update(id string, model MODEL) error
    Insert(model MODEL) error
    Delete(id string) error
}

這是一個大接口,你可以根據(jù)具體用例的需要縮短它,但是,為了完整性起見,我們暫時就這么寫。

現(xiàn)在你可以定義一個使用 DataProvider 的 HTTP 處理程序:

type HTTPHandler[MODEL Model] struct {
    dataProvider DataProvider[MODEL]
}

func (h HTTPHandler[MODEL]) FindByID(rw http.ResponseWriter, req *http.Request) {
    // validate request here
    id = // extract id here
    model, err := h.dataProvider.FindByID(id)
    if err != nil {
        // error handling here
        return
    }
    err = json.NewEncoder(rw).Encode(model)
    if err != nil {
        // error handling here
        return
    }
}

如你所見,我們可以為每個方法實現(xiàn)一次,然后我們就完成了。我們甚至可以在事物的另一端創(chuàng)建一個客戶端,我們只需要為基本方法實現(xiàn)一次。

為什么我們在這里使用泛型而不是簡單的我們已經(jīng)定義的 Model 接口?

與在此處使用 Model 類型本身相比,泛型有一些優(yōu)點:

  1. 使用泛型方法,DataProvider 根本不需要知道 Model,也不需要實現(xiàn)它。它可以簡單地提供非常強大的具體類型(但仍然可以為簡單的用例抽象)
  2. 我們可以擴展這個解決方案并使用具體類型進行操作。讓我們看看插入或更新的驗證器會是什么樣子。
type HTTPHandler[MODEL any] struct {
    dataProvider DataProvider[MODEL]
    InsertValidator func(new MODEL) error
    UpdateValidator func(old MODEL, new MODEL) error
}

在這個驗證器中是泛型方法的真正優(yōu)勢所在。我們將解析 HTTP 請求,如果定義了自定義的 InsertValidator,那么我們可以使用它來驗證模型是否檢出,我們可以以類型安全的方式進行并使用具體模型:

type User struct {
    FirstName string
    LastName string
}

func InsertValidator(u User) error {
    if u.FirstName == "" { ... } 
    if u.LastName == "" { ... }
}

所以我們有一個泛型的處理器,我們可以用自定義回調來調整它,它直接為我們獲取有效負載。沒有類型轉換。沒有 map。只有結構體本身!

03 不好的應用場景

一起看一個泛型日志器的例子:

type GenericLogger[T any] interface {
    WithField(string, string) T
    Info(string)
}

這本身還不是很有用。有更簡單的方法可以將鍵值字符串對添加到日志器,并且沒有日志器(據(jù)我所知)實際實現(xiàn)此接口。我們也不需要新的日志標準。如果我們想使用 logrus[1],我們必須這樣做:

type GenericLogger[T any, FIELD map[string]interface{}] interface{
    WithFields(M) T
    Info(string)
}

如果我們添加自引用部分,這實際上可能由 logrus 日志器實現(xiàn)。但是,讓我們考慮在實際結構體中使用它,例如某種處理程序:

type MessageHandler[T GenericLogger[T], FIELD map[string]interface{}] struct {
    logger GenericLogger[T, FIELD]
}

為了在結構體中使用這個日志器,我們需要使我們的結構體泛型,這僅適用于日志器。如果 MessageHandler 本身正在處理泛型消息,那將變成第三個類型參數(shù)!

到目前為止,甚至沒有辦法將其分配給具有泛型的變量。所以,盡管我們可以用一個接口來表示這個日志器很棒,但我實際上建議不要這樣做。而我最喜歡的日志庫 (zap[2]),由于其字段的性質,甚至無法用它來表示。

04 丑的場景

當我使用泛型時,我發(fā)現(xiàn)缺少對在方法中引入新泛型參數(shù)的支持。雖然這可能有很好的理由,但它確實需要一些解決方法。讓我們想象一下我們想要將一個 map 簡化為一個整數(shù)。理想情況下,我們將通過使用返回新泛型參數(shù)的方法來完成此操作,然后我們可以簡單地提供 map reduce 函數(shù)。

那么,當我們仍然想以泛型方式縮小該 map 時,我們該怎么辦?既然沒有方法,那么讓我們創(chuàng)建一個方法:

type GenericMap[KEY comparable, VALUE any] map[KEY]VALUEfunc (g GenericMap[KEY, VALUE]) Values() []VALUE {
    values := make([]VALUE, len(g))
    for _, v := range g {
        values = Append(values, v)
    }
    return values
}

func Reduce[KEY comparable, VALUE any, RETURN any](g GenericMap[KEY, VALUE], callback func(RETURN, KEY, VALUE "KEY comparable, VALUE any, RETURN any") RETURN) RETURN {
    var r RETURN
    for k, v := range g {
        r = callback(r, k, v)
    }
    return r
}

GenericMap 成為第一個參數(shù)或我們的 Reduce 函數(shù)。在這種情況下,你可以使用任何類型的 map 作為第一個參數(shù),而不是 GenericMap。然而,我想說明的一點是,如果這個方法本身是 GenericMap 的一部分,那就太好了。即使不是,我們仍然可以模仿這種行為。總的來說,我可能仍會在某些用例中使用這種模式,即使它實際上很丑陋。

05 真的很丑

有時你可能想要使用工廠模式,它為你提供諸如 DataProviders 之類的東西。你可能希望在動態(tài)注冊的端點上獲取提供程序。所以你可以這樣做:

type DataProviderFactory struct {
    dataProviders map[providerKey]any
}
func ProviderByName[MODEL Model](factory *DataProviderFactory, name string "MODEL Model") (DataProvider[MODEL], bool) {
        var m MODEL
    prov, has := factory.dataProviders[providerKey{name: name, typ: reflect.TypeOf(m)}]
    if !has {
       return nil, false
    }
    return prov.(DataProvider[MODEL]), true 
}
func RegisterProvider[MODEL Model](factory *DataProviderFactory, name string, p DataProvider[MODEL] "MODEL Model") {
    var m MODEL
    factory.dataProviders[providerKey{name: name, typ: reflect.TypeOf(m)}] = p 
}

雖然這有效,雖然它可能有用,但它是很丑。它將丑陋(反射)與更丑陋(泛型)的東西結合在一起。

雖然從技術上講這應該是類型安全的,但由于我們的復合鍵具有名稱和反射類型,它仍然很難看。我是否要把它放在生產代碼的任何地方,我會很糾結。

06 總結

雖然我喜歡泛型,但我認為很難取得平衡,尤其是在開始的時候。所以我們需要確保記住它們?yōu)槭裁创嬖冢谑裁辞闆r下我們應該使用它們,什么時候我們應該避免它們!

原文鏈接:https://itnext.io/golang-1-18-generics-the-good-the-bad-the-ugly-5e9fa2520e76

參考資料

[1]

logrus: https://github.com/sirupsen/logrus

[2]

zap: https://github.com/uber-go/zap

分享到:
標簽:泛型
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定