Go 語言中怎麼使用依賴注入?
01 介紹
在 Go 語言項目開發中,我們處理組件層級之間的依賴關係時,通常我們會先在依賴層級的代碼中實例化被依賴層級,然後調用它的方法,即依賴方需要主動獲取被依賴方。
但是,當被依賴層級的代碼發生變化時,依賴層級的代碼也需要修改,耦合性比較高,代碼不方便擴展。
所謂依賴注入,即依賴方不再需要主動獲取被依賴方,而是被依賴方主動傳遞給依賴方。
本文我們介紹 Go 語言怎麼使用依賴注入。
02 Go 語言使用依賴注入
在 Go 語言中,怎麼使用依賴注入呢?我準備以 clean arch 架構的代碼講解。
推薦讀者朋友們先閱讀我之前寫的一遍文章 「Go 語言整潔架構實踐」。
參照 Bob 大叔的一篇關於整潔架構的文章 The Clean Architecture,我們分 4 個層級:
-
Models
-
Repository
-
Usecase
-
Delivery
限於篇幅,本文主要介紹在 Go 語言中使用構造函數的方式實現依賴注入,讀者朋友們可以在留言區分享其它實現方式。
示例代碼:
// Models 層
type Todolist struct {
Id int64 `json:"id"`
Title string `json:"title"`
Status int `json:"status"`
Created int `json:"created"`
Updated int `json:"updated"`
}
type TodoListRepository interface {
Create(ctx context.Context, t *Todolist) (err error)
}
type TodoListUsecase interface {
Create(context.Context, *Todolist) (err error)
}
// Repository 層
type mysqlTodoListRepository struct {
Conn *sql.DB
}
func NewMysqlTodoListRepository(Conn *sql.DB) models.TodoListRepository {
return &mysqlTodoListRepository{Conn}
}
func (m *mysqlTodoListRepository) Create(ctx context.Context, t *models.Todolist) (err error) {
// ...
return
}
// Usecase 層
type todoListUsecase struct {
todoListRepo models.TodoListRepository
}
func NewTodoListUsecase(t models.TodoListRepository) models.TodoListRepository {
return &todoListUsecase{
todoListRepo: t,
}
}
func (tl *todoListUsecase) Create(ctx context.Context, t *models.Todolist) (err error) {
if t.Title == "" {
return fmt.Errorf("illegal parameter")
}
return tl.todoListRepo.Create(ctx, t)
}
// Delivery 層
type TodoListHandler struct {
TodoListUsecase models.TodoListUsecase
}
func NewTodoListHandler(r *gin.Engine, todoListUsecase models.TodoListUsecase) {
handler := &TodoListHandler{
TodoListUsecase: todoListUsecase,
}
r.POST("/create", handler.Create)
r.Run()
}
// main 函數
func main() {
conn, err := sql.Open(`mysql`, "root:root@tcp(127.0.0.1:3306)/todolist")
if err != nil {
log.Fatal(err)
}
r := gin.Default()
todoListRepository := mysql.NewMysqlTodoListRepository(conn)
todoListUsecase := usecase.NewTodoListUsecase(todoListRepository)
http.NewTodoListHandler(r, todoListUsecase)
}
閱讀上面這段代碼,我們可以發現 Repository 層依賴數據庫驅動 conn,Usecase 層依賴 Repository 層,Delivery 層依賴 Usecase 層。
以 Repository 層和 Usecase 層爲例,我們可以發現 Usecase 層通過構造函數 func NewTodoListUsecase(t models.TodoListRepository) models.TodoListRepository
將其依賴項 models.TodoListRepository
以參數的形式傳遞過來,並將其放入 todoListUsecase
結構體中。
所以,我們使用 Usecase 層的構造函數 NewTodoListUsecase
創建 Usecase 對象時,需要先使用 Repository 層的構造函數 NewMysqlTodoListRepository
創建 Repository 對象,並將其以參數的形式傳遞給 Usecase 層的構造函數 NewTodoListUsecase
。
通過依賴注入的方式,可以有效降低組件層級之間的耦合性,方便代碼的擴展。比如示例代碼中 Repository 層的方法修改代碼,不會影響 Usecase 層的代碼。
03 依賴注入工具
除了手寫依賴注入代碼,我們也可以使用依賴注入工具,開源社區有很多依賴注入工具,其中比較流行的主要有以下 3 個。
Google 開源的依賴注入工具 Wire[1],它是一個代碼生成工具,也就是說它是在編譯時自動生成代碼。
另外 2 個依賴注入工具是在運行時基於 Go 反射實現,分別是 uber 開源的依賴注入工具 Dig[2] 和 facebook 開源的依賴注入工具 [3]。
讀者朋友們可以根據實際開發中的需求,選擇合適的工具。
04 總結
讀者朋友們可能已經發現,依賴注入實際上就是面向對象五大原則之一,依賴倒置原則的實現方式。
我們可以在 Go 項目開發中,使用依賴注入的方式,降低組件層級之間的代碼耦合性,使代碼更方便擴展。
參考資料
[1] Google 開源的依賴注入工具 Wire: https://github.com/google/wire
[2] uber 開源的依賴注入工具 Dig: https://github.com/uber-go/dig
[3] facebook 開源的依賴注入工具: https://github.com/facebookarchive/inject
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/R9G9dHMFMYwMiKIiTOetYg