最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入淺出go依賴注入工具Wire的使用

 更新時間:2023年09月18日 08:15:47   作者:陳明勇  
但隨著項目規(guī)模的增長,組件之間的依賴關(guān)系變得復(fù)雜,手動管理可能會很繁瑣,所以本文將深入探討一個備受歡迎的?Go?語言依賴注入工具——?Wire,感興趣的可以了解下

前言

在日常項目開發(fā)中,我們經(jīng)常會使用到依賴注入的設(shè)計模式,目的是為了降低代碼組件之間的耦合度,提高代碼的可維護(hù)性、可擴(kuò)展性和可測試性。

但隨著項目規(guī)模的增長,組件之間的依賴關(guān)系變得復(fù)雜,手動管理它們之間的依賴關(guān)系可能會很繁瑣。為了簡化這個過程,我們可以利用依賴注入代碼生成工具,它可以自動為我們生成所需的代碼,從而減輕了手動處理依賴注入的繁重工作。

Go 語言有許多依賴注入的工具,而本文將深入探討一個備受歡迎的 Go 語言依賴注入工具—— Wire。

準(zhǔn)備好了嗎?準(zhǔn)備一杯你最喜歡的咖啡或茶,隨著本文一探究竟吧。

Wire

Wire 是一個專為依賴注入(Dependency Injection)設(shè)計的代碼生成工具,它可以自動生成用于初始化各種依賴關(guān)系的代碼,從而幫助我們更輕松地管理和注入依賴關(guān)系。

Wire 安裝

我們可以執(zhí)行以下命令來安裝 Wire 工具:

go install github.com/google/wire/cmd/wire@latest

安裝之前請確保已將 $GOPATH/bin 添加到環(huán)境變量 $PATH 里。

Wire 的基本使用

前置代碼準(zhǔn)備

雖然我們在前面已經(jīng)通過 go install 命令安裝了 Wire 命令行工具,但在具體項目中,我們?nèi)匀恍枰ㄟ^以下命令安裝項目所需的 Wire 依賴,以便結(jié)合 Wire 工具生成代碼:

go get github.com/google/wire@latest

接下來,讓我們模擬一個簡單的 web 博客項目,編寫查詢文章接口的相關(guān)代碼,并使用 Wire 工具生成代碼。

首先,我們先定義相關(guān)類型與方法,并提供對應(yīng)的 初始化函數(shù)

定義 PostHandler 結(jié)構(gòu)體,創(chuàng)建注冊路由的方法 RegisterRoutes 和查詢文章路由處理的方法 GetPostById 以及初始化的函數(shù) NewPostHandler,并且它依賴于 IPostService 接口:

package handler
import (
    "chenmingyong0423/blog/tutorial-code/wire/internal/post/service"
    "github.com/gin-gonic/gin"
    "net/http"
)
type PostHandler struct {
    serv service.IPostService
}
func (h *PostHandler) RegisterRoutes(engine *gin.Engine) {
    engine.GET("/post/:id", h.GetPostById)
}
func (h *PostHandler) GetPostById(ctx *gin.Context) {
    content := h.serv.GetPostById(ctx, ctx.Param("id"))
    ctx.String(http.StatusOK, content)
}
func NewPostHandler(serv service.IPostService) *PostHandler {
    return &PostHandler{serv: serv}
}

定義 IPostService 接口,并提供了一個具體實現(xiàn) PostService,接著創(chuàng)建 GetPostById 方法,用于處理查詢文章的邏輯,然后提供初始化函數(shù) NewPostService,該函數(shù)返回 IPostService 接口類型:

package service
import (
    "context"
    "fmt"
)
type IPostService interface {
    GetPostById(ctx context.Context, id string) string
}
var _ IPostService = (*PostService)(nil)
type PostService struct {
}
func (s *PostService) GetPostById(ctx context.Context, id string) string {
    return fmt.Sprint("歡迎關(guān)注本掘金號,作者:陳明勇")
}
func NewPostService() IPostService {
    return &PostService{}
}

定義一個初始化 gin.Engine 函數(shù) NewGinEngineAndRegisterRoute,該函數(shù)依賴于 *handler.PostHandler 類型,函數(shù)內(nèi)部調(diào)用相關(guān) handler 結(jié)構(gòu)體的方法創(chuàng)建路由:

package ioc
import (
    "chenmingyong0423/blog/tutorial-code/wire/internal/post/handler"
    "github.com/gin-gonic/gin"
)
func NewGinEngineAndRegisterRoute(postHandler *handler.PostHandler) *gin.Engine {
    engine := gin.Default()
    postHandler.RegisterRoutes(engine)
    return engine
}

使用 Wire 工具生成代碼

前置代碼已經(jīng)準(zhǔn)備好了,接下來我們編寫核心代碼,以便 Wire 工具能生成相應(yīng)的依賴注入代碼。

首先我們需要創(chuàng)建一個 wire 的配置文件,通常命名為 wire.go。在這個文件里,我們需要定義一個或者多個注入器函數(shù)(Injector 函數(shù),接下來的內(nèi)容會對其進(jìn)行解釋),以便指引 Wire 工具生成代碼。

//go:build wireinject
package wire
import (
    "chenmingyong0423/blog/tutorial-code/wire/internal/post/handler"
    "chenmingyong0423/blog/tutorial-code/wire/internal/post/service"
    "chenmingyong0423/blog/tutorial-code/wire/ioc"
    "github.com/gin-gonic/gin"
    "github.com/google/wire"
)
func InitializeApp() *gin.Engine {
    wire.Build(
       handler.NewPostHandler,
       service.NewPostService,
       ioc.NewGinEngineAndRegisterRoute,
    )
    return &gin.Engine{}
}

在上述代碼中,我們定義了一個用于初始化 gin.Engine 的注入器函數(shù),在該函數(shù)內(nèi)部,我們使用了 wire.Build 方法來聲明依賴關(guān)系,其中包括 PostHandler、PostServiceInitGinEngine 作為依賴的構(gòu)造函數(shù)。

wire.Build 的作用是 連接或綁定我們之前定義的所有初始化函數(shù)。當(dāng)我們運(yùn)行 wire 工具來生成代碼時,它就會根據(jù)這些依賴關(guān)系來自動創(chuàng)建和注入所需的實例。

注意:文件首行必須加上 //go:build wireinject// +build wireinject(go 1.18 之前的版本使用) 注釋,作用是只有在使用 wire 工具時才會編譯這部分代碼,其他情況下忽略。

接下來在 wire.go 文件所處目錄下執(zhí)行 wire 命令,生成 wire_gen.go 文件,內(nèi)容如下所示:

// Code generated by Wire. DO NOT EDIT.
//go:generate go run github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package wire
import (
    "chenmingyong0423/blog/tutorial-code/wire/internal/post/handler"
    "chenmingyong0423/blog/tutorial-code/wire/internal/post/service"
    "chenmingyong0423/blog/tutorial-code/wire/ioc"
    "github.com/gin-gonic/gin"
)
// Injectors from wire.go:
func InitializeApp() *gin.Engine {
    iPostService := service.NewPostService()
    postHandler := handler.NewPostHandler(iPostService)
    engine := ioc.NewGinEngineAndRegisterRoute(postHandler)
    return engine
}

生成的代碼和我們手寫區(qū)別不大,當(dāng)我們的組件很多,依賴關(guān)系復(fù)雜的時候,我們才會感覺到 Wire 工具的好處。

Wire 的核心概念

Wire 有兩個核心概念:提供者(providers)和注入器(injectors)。

Wire 提供者(providers)

提供者:一個可以產(chǎn)生值的函數(shù),也就是有返回值的函數(shù)。例如入門代碼里的 NewPostHandler 函數(shù):

func NewPostHandler(serv service.IPostService) *PostHandler {
    return &PostHandler{serv: serv}
}

返回值不僅限于一個,如果有需要的話,可以額外添加一個 error 的返回值。

如果提供者過多的時候,我們還可以以分組的形式進(jìn)行連接,例如將 post 相關(guān)的 handlerservice 進(jìn)行組合:

package handler
var PostSet = wire.NewSet(NewPostHandler, service.NewPostService)

使用 wire.NewSet 函數(shù)將提供者進(jìn)行分組,該函數(shù)返回一個 ProviderSet 結(jié)構(gòu)體。不僅如此,wire.NewSet 還能對多個 ProviderSet 進(jìn)行分組 wire.NewSet(PostSet, XxxSet) 。

對于之前的 InitializeApp 函數(shù),我們可以這樣升級:

//go:build wireinject
package wire
func InitializeAppV2() *gin.Engine {
    wire.Build(
       handler.PostSet,
       ioc.NewGinEngineAndRegisterRoute,
    )
    return &gin.Engine{}
}

然后通過 Wire 命令生成代碼,和之前的結(jié)果一致。

Wire 注入器(injectors)

注入器(injectors)的作用是將所有的提供者(providers)連接起來,回顧一下我們之前的代碼:

func InitializeApp() *gin.Engine {
    wire.Build(
       handler.NewPostHandler,
       service.NewPostService,
       ioc.NewGinEngineAndRegisterRoute,
    )
    return &gin.Engine{}
}

InitializeApp 函數(shù)就是一個注入器,函數(shù)內(nèi)部通過 wire.Build 函數(shù)連接所有的提供者,然后返回 &gin.Engine{},該返回值實際上并沒有使用到,只是為了滿足編譯器的要求,避免報錯而已,真正的返回值來自 ioc.NewGinEngineAndRegisterRoute。

Wire 的高級用法

綁定接口

回顧我們之前編寫的代碼:

package handler
···
func NewPostHandler(serv service.IPostService) *PostHandler {
    return &PostHandler{serv: serv}
}
···
pakacge service
···
func NewPostService() IPostService {
    return &PostService{}
}
···

NewPostHandler 函數(shù)依賴于 service.IPostService 接口,NewPostService 函數(shù)返回的是 IPostService 接口的值,這兩個地方的類型匹配,因此 Wire 工具能夠正確識別并生成代碼。然而,這并不是推薦的最佳實踐。因為在 Go 中的 最佳實踐 是返回 具體的類型 的值,所以最好讓 NewPostService 返回具體類型 PostService 的值:

func NewPostServiceV2() *PostService {
    return &PostService{}
}

但是這樣,Wire 工具將認(rèn)為 IPostService 接口類型與 PostService 類型不匹配,導(dǎo)致生成代碼失敗。因此我們需要修改注入器的代碼:

func InitializeAppV3() *gin.Engine {
    wire.Build(
       handler.NewPostHandler,
       service.NewPostServiceV2,
       ioc.NewGinEngineAndRegisterRoute,
       wire.Bind(new(service.IPostService), new(*service.PostService)),
    )
    return &gin.Engine{}
}

使用 wire.Bind 來建立接口類型和具體的實現(xiàn)類型之間的綁定關(guān)系,這樣 Wire 工具就可以根據(jù)這個綁定關(guān)系進(jìn)行類型匹配并生成代碼。

wire.Bind 函數(shù)的第一個參數(shù)是指向所需接口類型值的指針,第二個實參是指向?qū)崿F(xiàn)該接口的類型值的指針。

結(jié)構(gòu)體提供者(Struct Providers)

Wire 庫有一個函數(shù)是 wire.Struct,它能根據(jù)現(xiàn)有的類型進(jìn)行構(gòu)造結(jié)構(gòu)體,我們來看看下面的例子:

package main
type Name string
func NewName() Name {
    return "陳明勇"
}
type PublicAccount string
func NewPublicAccount() PublicAccount {
    return "公眾號:Go技術(shù)干貨"
}
type User struct {
    MyName          Name
    MyPublicAccount PublicAccount
}
func InitializeUser() *User {
    wire.Build(
       NewName,
       NewPublicAccount,
       wire.Struct(new(User), "MyName", "MyPublicAccount"),
    )
    return &User{}
}

上述代碼中,首先定義了自定義類型 NamePublicAccount 以及結(jié)構(gòu)體類型 User,并分別提供了 NamePublicAccount 的初始化函數(shù)(providers)。然后定義一個注入器(injectorsInitializeUser,用于構(gòu)造連接提供者并構(gòu)造 *User 實例。

使用 wire.Struct 函數(shù)需要傳遞兩個參數(shù),第一個參數(shù)是結(jié)構(gòu)體類型的指針值,另一個參數(shù)是一個可變參數(shù),表示需要注入的結(jié)構(gòu)體字段的名稱集。

根據(jù)上述代碼,使用 Wire 工具生成的代碼如下所示:

func InitializeUser() *User {
    name := NewName()
    publicAccount := NewPublicAccount()
    user := &User{
       MyName:          name,
       MyPublicAccount: publicAccount,
    }
    return user
}

如果我們不想返回指針類型,只需要修改 InitializeUser 函數(shù)的返回值為非指針即可。

綁定值

有時候,我們可以在注入器中通過 值表達(dá)式 給一個類型進(jìn)行賦值,而不是依賴提供者(providers)。

func InjectUser() User {
    wire.Build(wire.Value(User{MyName: "陳明勇"}))
    return User{}
}

在上述代碼中,使用 wire.Value 函數(shù)通過表達(dá)式直接指定 MyName 的值,生成的代碼如下所示:

func InjectUser() User {
    user := _wireUserValue
    return user
}
var (
    _wireUserValue = User{MyName: "陳明勇"}
)

需要注意的是,值表達(dá)式將被復(fù)制到生成的代碼文件中。

對于接口類型,可以使用 InterfaceValue

func InjectPostService() service.IPostService {
    wire.Build(wire.InterfaceValue(new(service.IPostService), &service.PostService{}))
    return nil
}

使用結(jié)構(gòu)體字段作為提供者(providers)

有些時候,你可以使用結(jié)構(gòu)體的某個字段作為提供者,從而生成一個類似 GetXXX 的函數(shù)。

func GetUserName() Name {
    wire.Build(
       NewUser,
       wire.FieldsOf(new(User), "MyName"),
    )
    return ""
}

你可以使用 wire.FieldsOf 函數(shù)添加任意字段,生成的代碼如下所示:

func GetUserName() Name {
    user := NewUser()
    name := user.MyName
    return name
}
func NewUser() User {
    return User{MyName: Name("陳明勇"), MyPublicAccount: PublicAccount("公眾號:Go技術(shù)干貨")}
}

清理函數(shù)

如果一個提供者創(chuàng)建了一個需要清理的值(例如關(guān)閉一個文件),那么它可以返回一個閉包來清理資源。注入器會用它來給調(diào)用者返回一個聚合的清理函數(shù),或者在注入器實現(xiàn)中稍后調(diào)用的提供商返回錯誤時清理資源。

func provideFile(log Logger, path Path) (*os.File, func(), error) {
    f, err := os.Open(string(path))
    if err != nil {
        return nil, nil, err
    }
    cleanup := func() {
        if err := f.Close(); err != nil {
            log.Log(err)
        }
    }
    return f, cleanup, nil
}

備用注入器語法

如果你不喜歡將類似這種寫法 → return &gin.Engine{} 放在你的注入器函數(shù)聲明的末尾,你可以用 panic 來更簡潔地寫它:

func InitializeGin() *gin.Engine {
    panic(wire.Build(/* ... */))
}

小結(jié)

在本文中,我們詳細(xì)探討了 Go Wire 工具的基本用法和高級特性。它是一個專為依賴注入設(shè)計的代碼生成工具,它不僅提供了基礎(chǔ)的依賴解析和代碼生成功能,還支持多種高級用法,如接口綁定和構(gòu)造結(jié)構(gòu)體。

依賴注入的設(shè)計模式應(yīng)用非常廣泛,Wire 工具讓依賴注入在 Go 語言中變得更簡單。

以上就是深入淺出go依賴注入工具Wire的使用的詳細(xì)內(nèi)容,更多關(guān)于go依賴注入工具Wire的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 詳解Go語言如何對數(shù)據(jù)庫進(jìn)行CRUD操作

    詳解Go語言如何對數(shù)據(jù)庫進(jìn)行CRUD操作

    在這篇文章中,主要帶大家來學(xué)習(xí)一下在Go語言中如何對數(shù)據(jù)庫進(jìn)行CRUD操作,從而探討一下Go的接口編程,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-05-05
  • go?語言爬蟲庫goquery的具體使用

    go?語言爬蟲庫goquery的具體使用

    GoQuery是專為Go語言設(shè)計的一個強(qiáng)大的HTML解析和查詢庫,本文主要介紹了go語言爬蟲庫goquery的具體使用,具有一定的參考價值,感興趣的可以了解一下
    2024-01-01
  • 基于Golang?container/list實現(xiàn)LRU緩存

    基于Golang?container/list實現(xiàn)LRU緩存

    Least?Recently?Used?(LRU)?,即逐出最早使用的緩存,這篇文章主要為大家介紹了如何基于Golang?container/list實現(xiàn)LRU緩存,感興趣的可以了解下
    2023-08-08
  • golang基于errgroup實現(xiàn)并發(fā)調(diào)用的方法

    golang基于errgroup實現(xiàn)并發(fā)調(diào)用的方法

    這篇文章主要介紹了golang基于errgroup實現(xiàn)并發(fā)調(diào)用,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • golang 實現(xiàn)時間滑動窗口的示例代碼

    golang 實現(xiàn)時間滑動窗口的示例代碼

    滑動時間窗口就是把一段時間片分為多個樣本窗口,可以通過更細(xì)粒度對數(shù)據(jù)進(jìn)行統(tǒng)計,這篇文章主要介紹了golang 實現(xiàn)時間滑動窗口,需要的朋友可以參考下
    2022-10-10
  • golang如何去除 context 的 deadline

    golang如何去除 context 的 deadline

    在使用 context 的時候遇到了開協(xié)程處理任務(wù)的情況,但是直接在協(xié)程里使用主線程的 context 會導(dǎo)致當(dāng)主線程返回時協(xié)程任務(wù)也會因為 context cancel 而失敗,本文提供了兩種辦法可以取消掉 context 里的 timeout 和 deadline,再設(shè)置一個新的 timeout 上去
    2023-03-03
  • 淺談Golang數(shù)據(jù)競態(tài)

    淺談Golang數(shù)據(jù)競態(tài)

    本文主要介紹了淺談Golang數(shù)據(jù)競態(tài),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Go語言讀取YAML 配置文件的兩種方式分享

    Go語言讀取YAML 配置文件的兩種方式分享

    在日常開發(fā)中,YAML 格式的文件基本上被默認(rèn)為是配置文件,其內(nèi)容因為縮進(jìn)帶來的層級感看起來非常直觀和整潔。本文分享了讀取YAML 配置文件的兩種方式,需要的可以參考一下
    2022-12-12
  • Golang使用gin框架實現(xiàn)一個完整的聊天室功能

    Golang使用gin框架實現(xiàn)一個完整的聊天室功能

    由于我們項目的需要,我就研究了一下關(guān)于websocket的相關(guān)內(nèi)容,去實現(xiàn)一個聊天室的功能,經(jīng)過幾天的探索,現(xiàn)在使用Gin框架實現(xiàn)了一個完整的聊天室+消息實時通知系統(tǒng),感興趣的小伙伴歡迎閱讀本文
    2023-08-08
  • Go?Redis客戶端使用的兩種對比

    Go?Redis客戶端使用的兩種對比

    這篇文章主要為大家介紹了Go?Redis客戶端使用對比詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07

最新評論

贺州市| 平乡县| 梁山县| 平凉市| 彰化市| 栾川县| 泰顺县| 开封县| 平度市| 甘洛县| 剑河县| 洛扎县| 和林格尔县| 吕梁市| 吉林市| 抚松县| 沂水县| 宁波市| 三都| 海南省| 微山县| 锡林浩特市| 德州市| 卢龙县| SHOW| 苏尼特左旗| 石河子市| 双鸭山市| 黎城县| 石家庄市| 麦盖提县| 佛山市| 丁青县| 达州市| 涞水县| 皋兰县| 池州市| 萨嘎县| 临夏县| 扎鲁特旗| 大化|