<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go1.18 泛型的好、壞亦或丑?

          共 4619字,需瀏覽 10分鐘

           ·

          2022-01-22 10:26

          Go 泛型定了,有哪些好的使用場(chǎng)景,哪些不好的應(yīng)用場(chǎng)景,亦或哪些使用看起來(lái)丑?本文聊聊這個(gè)問(wèn)題。

          01 簡(jiǎn)介

          泛型很棒,而且 Go 變得比以前更方便了。但是與可能非常有用的 channel 類似,我們不應(yīng)該僅僅因?yàn)樗鼈兇嬖诰偷教幨褂盟鼈儭?/p>

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

          讓我們分別看一個(gè)例子!

          02 好的應(yīng)用場(chǎng)景

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

          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
          }

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

          現(xiàn)在你可以定義一個(gè)使用 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
          ????}
          }

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

          為什么我們?cè)谶@里使用泛型而不是簡(jiǎn)單的我們已經(jīng)定義的 Model 接口?

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

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

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

          type?User?struct?{
          ????FirstName?string
          ????LastName?string
          }

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

          所以我們有一個(gè)泛型的處理器,我們可以用自定義回調(diào)來(lái)調(diào)整它,它直接為我們獲取有效負(fù)載。沒(méi)有類型轉(zhuǎn)換。沒(méi)有 map。只有結(jié)構(gòu)體本身!

          03 不好的應(yīng)用場(chǎng)景

          一起看一個(gè)泛型日志器的例子:

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

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

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

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

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

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

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

          04 丑的場(chǎng)景

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

          那么,當(dāng)我們?nèi)匀幌胍苑盒头绞娇s小該 map 時(shí),我們?cè)撛趺崔k?既然沒(méi)有方法,那么讓我們創(chuàng)建一個(gè)方法:

          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 成為第一個(gè)參數(shù)或我們的 Reduce 函數(shù)。在這種情況下,你可以使用任何類型的 map 作為第一個(gè)參數(shù),而不是 GenericMap。然而,我想說(shuō)明的一點(diǎn)是,如果這個(gè)方法本身是 GenericMap 的一部分,那就太好了。即使不是,我們?nèi)匀豢梢阅7逻@種行為。總的來(lái)說(shuō),我可能仍會(huì)在某些用例中使用這種模式,即使它實(shí)際上很丑陋。

          05 真的很丑

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

          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?
          }

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

          雖然從技術(shù)上講這應(yīng)該是類型安全的,但由于我們的復(fù)合鍵具有名稱和反射類型,它仍然很難看。我是否要把它放在生產(chǎn)代碼的任何地方,我會(huì)很糾結(jié)。

          06 總結(jié)

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

          原文鏈接: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




          往期推薦


          我是 polarisxu,北大碩士畢業(yè),曾在 360 等知名互聯(lián)網(wǎng)公司工作,10多年技術(shù)研發(fā)與架構(gòu)經(jīng)驗(yàn)!2012 年接觸 Go 語(yǔ)言并創(chuàng)建了 Go 語(yǔ)言中文網(wǎng)!著有《Go語(yǔ)言編程之旅》、開(kāi)源圖書《Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)》等。


          堅(jiān)持輸出技術(shù)(包括 Go、Rust 等技術(shù))、職場(chǎng)心得和創(chuàng)業(yè)感悟!歡迎關(guān)注「polarisxu」一起成長(zhǎng)!也歡迎加我微信好友交流:gopherstudio

          瀏覽 84
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲黄色视频免费 | 婷婷五月天综合色 | 丁香五月在线视频 | 国产又黄又湿 | 欧美日韩日逼 |