欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

前言

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

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

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

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

Wire

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

Wire 安裝

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

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

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

Wire 的基本使用

前置代碼準(zhǔn)備

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

go get github.com/google/wire@latest

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

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

定義 PostHandler 結(jié)構(gòu)體,創(chuàng)建注冊(cè)路由的方法 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 接口,并提供了一個(gè)具體實(shí)現(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)注本掘金號(hào),作者:陳明勇")
}
func NewPostService() IPostService {
    return &PostService{}
}

定義一個(gè)初始化 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)的依賴注入代碼。

首先我們需要?jiǎng)?chuàng)建一個(gè) wire 的配置文件,通常命名為 wire.go。在這個(gè)文件里,我們需要定義一個(gè)或者多個(gè)注入器函數(shù)(Injector 函數(shù),接下來的內(nèi)容會(huì)對(duì)其進(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{}
}

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

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

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

接下來在 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ù)雜的時(shí)候,我們才會(huì)感覺到 Wire 工具的好處。

Wire 的核心概念

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

Wire 提供者(providers)

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

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

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

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

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

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

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

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

Wire 的高級(jí)用法

綁定接口

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

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 接口的值,這兩個(gè)地方的類型匹配,因此 Wire 工具能夠正確識(shí)別并生成代碼。然而,這并不是推薦的最佳實(shí)踐。因?yàn)樵?Go 中的 最佳實(shí)踐 是返回 具體的類型 的值,所以最好讓 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 來建立接口類型和具體的實(shí)現(xiàn)類型之間的綁定關(guān)系,這樣 Wire 工具就可以根據(jù)這個(gè)綁定關(guān)系進(jìn)行類型匹配并生成代碼。

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

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

Wire 庫(kù)有一個(gè)函數(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 "公眾號(hào):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)。然后定義一個(gè)注入器(injectorsInitializeUser,用于構(gòu)造連接提供者并構(gòu)造 *User 實(shí)例。

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

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

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

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

綁定值

有時(shí)候,我們可以在注入器中通過 值表達(dá)式 給一個(gè)類型進(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ù)制到生成的代碼文件中。

對(duì)于接口類型,可以使用 InterfaceValue

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

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

有些時(shí)候,你可以使用結(jié)構(gòu)體的某個(gè)字段作為提供者,從而生成一個(gè)類似 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("公眾號(hào):Go技術(shù)干貨")}
}

清理函數(shù)

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

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
}

備用注入器語(yǔ)法

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

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

小結(jié)

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

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

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

相關(guān)文章

  • Golang 中的可測(cè)試示例函數(shù)(Example Function)詳解

    Golang 中的可測(cè)試示例函數(shù)(Example Function)詳解

    這篇文章詳細(xì)講解了 Golang 中的可測(cè)試示例函數(shù),示例函數(shù)類似于單元測(cè)試函數(shù),但沒有  *testing 類型的參數(shù),編寫示例函數(shù)也是很容易的,本文就通過代碼示例給大家介紹一下Golang的可測(cè)試示例函數(shù),需要的朋友可以參考下
    2023-07-07
  • Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型

    Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型

    這篇文章主要介紹了Go泛型實(shí)戰(zhàn)教程之如何在結(jié)構(gòu)體中使用泛型,根據(jù)Go泛型使用的三步曲提到的:類型參數(shù)化、定義類型約束、類型實(shí)例化我們一步步來定義我們的緩存結(jié)構(gòu)體,需要的朋友可以參考下
    2022-07-07
  • Go切片導(dǎo)致rand.Shuffle產(chǎn)生重復(fù)數(shù)據(jù)的原因與解決方案

    Go切片導(dǎo)致rand.Shuffle產(chǎn)生重復(fù)數(shù)據(jù)的原因與解決方案

    在 Go 語(yǔ)言的實(shí)際開發(fā)中,切片(slice)是一種非常靈活的數(shù)據(jù)結(jié)構(gòu),然而,由于其底層數(shù)據(jù)共享的特性,在某些情況下可能會(huì)導(dǎo)致意想不到的 Bug,本文將詳細(xì)分析 rand.Shuffle 之后,切片中的數(shù)據(jù)出現(xiàn)重復(fù)的問題,探討其根本原因,并給出最佳解決方案,需要的朋友可以參考下
    2025-02-02
  • go?mod?tidy報(bào)錯(cuò):zip:?not?a?valid?zip?file解決辦法

    go?mod?tidy報(bào)錯(cuò):zip:?not?a?valid?zip?file解決辦法

    這篇文章主要給大家介紹了關(guān)于go?mod?tidy報(bào)錯(cuò):zip:?not?a?valid?zip?file的解決辦法,go mod是進(jìn)行代碼管理,這錯(cuò)誤是因?yàn)楸镜胤种Ш瓦h(yuǎn)程分支沖突,本文通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Go語(yǔ)言新寵:pdqsort排序算法的完美打造

    Go語(yǔ)言新寵:pdqsort排序算法的完美打造

    pdqsort是一種新的排序算法,特別適用于Go語(yǔ)言,它是由Go語(yǔ)言團(tuán)隊(duì)開發(fā)的,旨在提供高效且穩(wěn)定的排序算法,pdqsort采用了一種分治的策略,將數(shù)組分成小塊進(jìn)行排序,然后再合并這些塊,需要的朋友可以參考下
    2023-10-10
  • golang中的空slice案例

    golang中的空slice案例

    這篇文章主要介紹了golang中的空slice案例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-04-04
  • 使用gRPC實(shí)現(xiàn)獲取數(shù)據(jù)庫(kù)版本

    使用gRPC實(shí)現(xiàn)獲取數(shù)據(jù)庫(kù)版本

    這篇文章主要為大家詳細(xì)介紹了如何使用gRPC實(shí)現(xiàn)獲取數(shù)據(jù)庫(kù)版本,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-12-12
  • 如何使用Golang創(chuàng)建與讀取Excel文件

    如何使用Golang創(chuàng)建與讀取Excel文件

    我最近工作忙于作圖,圖表,需要自己準(zhǔn)備數(shù)據(jù)源,所以經(jīng)常和Excel打交道,下面這篇文章主要給大家介紹了關(guān)于如何使用Golang創(chuàng)建與讀取Excel文件的相關(guān)資料,需要的朋友可以參考下
    2022-07-07
  • Go語(yǔ)言掃描目錄并獲取相關(guān)信息的方法

    Go語(yǔ)言掃描目錄并獲取相關(guān)信息的方法

    這篇文章主要介紹了Go語(yǔ)言掃描目錄并獲取相關(guān)信息的方法,實(shí)例分析了Go語(yǔ)言操作目錄及文件的技巧,需要的朋友可以參考下
    2015-03-03
  • Go 并發(fā)讀寫 sync.map 詳細(xì)

    Go 并發(fā)讀寫 sync.map 詳細(xì)

    閱讀本文你將會(huì)明確 sync.Map 和原生 map +互斥鎖/讀寫鎖之間的性能情況。標(biāo)準(zhǔn)庫(kù) sync.Map 雖說支持并發(fā)讀寫 map,但更適用于讀多寫少的場(chǎng)景,因?yàn)樗麑懭氲男阅鼙容^差,使用時(shí)要考慮清楚這一點(diǎn)。
    2021-10-10

最新評(píng)論