Go 中正確使用依賴注入

轉自:

https://juejin.cn/post/7130001318046859272

依賴注入

依賴注入可以降低代碼的耦合度。某功能依賴另一個實現,但是與該實現無任何責任。它只接收該實現並使用該實現,可以通過依賴注入方式進行解耦。比如以下例子,UserService 依賴很多模塊。

Service 依賴很多模塊,我們可以將這部分內容都設置到 UserService 中,但這樣耦合太高了。

type UserService struct{}
type logger struct{}
type RoleService struct{}
type UserProvider struct{}

func (uServ *UserService)GetUser()error{
    loger := &logger{}
    provider := &UserProvider{}
    ....
    return nil
}

在 SOLID 原則中的 D,依賴倒置原則。說的是,程序要依賴於抽象接口,不要依賴於具體實現。注入依賴的一個重點時要避免注入具體的實現,即應該避免注入像 logger 、UserProvider 等結構。

依賴注入方式

構造函數注入

構造函數方式注入,在創建實例時就注入依賴項,而後就不能再改變。所以在使用時所有的依賴項都必須創建好,否則會產生錯誤。在 Go 中沒有構造函數的定義,所以可以使用 New 函數充當構造函數。

func NewUserService(log Logger, userProvider UserProvider) UserService {
    return &UserService{
        logger: log,
        userProvider: userProvider
    }
}

屬性和方法注入

屬性注入和方法注入非常相似。在 Java 中比較常見的是方法注入,而在 C# 中比較常見是屬性注入。在 Go 中也支持這兩種方式的注入,且大部分時候可以結合起來。

type UserService struct{
    logger MyLogger
    provider UserProvider
}
func (s *UserService)GetUser(log MyLogger){
    s.logger = log
}

定義 UserService 結構體中也依賴了 MyLogger 和 UserProvider 的結構體,方法 GetUser 參數是 MyLogger,這就是方法注入。

按照這樣設計時,這種注入是對於具體實現允許可變的,MyLogger 可能會有不同的實現,調用 GetUser 時只需傳入對應 MyLogger 類型即可,且不關心是何種具體的實現。

使用第三方包

使用手動的方式構造函數注入方式的話,如果有很多的依賴項,那麼就會看到傳入的參數就會很多, 就要手動去創建所有的依賴,無疑是種增加維護成本的操作。

type UserService struct{}

func NewUserService(logger MyLogger,repositroy MyRepositroy,provider UserProvider,...){
    // ...
}

// 使用
func main(){
    // 所有的依賴項都聲明,創建
    logger := &MyLogger{}
    repositroy := &MyRepositroy{}
    provider := &UserProvider{}
    ...
    // 所有的依賴項都注入
  userService := NewUserService(logger,repositroy,provider...)

}

像 Java 中通過一個能夠發現依賴並創建依賴關係的容器。只要通過它就可以自動識別關係並創建處其所依賴的對象。

所以我們可以構造一個這樣的容器,再通過反射,讀取函數的接收和返回每個參數的類型,就可以構建其所以依賴其他實例,並創建出依賴關係圖。

這裏可以推薦使用第三方 uber-go/dig 包(github.com/uber-go/dig%EF%BC%89%E3%80%82)

參考資料:

Go 開發大全

參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Fod5KTtAaU-S2ZDiupyj_Q