Go 語(yǔ)言中怎么使用依賴注入?
1 、介紹
在 Go 語(yǔ)言項(xiàng)目開發(fā)中,我們處理組件層級(jí)之間的依賴關(guān)系時(shí),通常我們會(huì)先在依賴層級(jí)的代碼中實(shí)例化被依賴層級(jí),然后調(diào)用它的方法,即依賴方需要主動(dòng)獲取被依賴方。
但是,當(dāng)被依賴層級(jí)的代碼發(fā)生變化時(shí),依賴層級(jí)的代碼也需要修改,耦合性比較高,代碼不方便擴(kuò)展。
所謂依賴注入,即依賴方不再需要主動(dòng)獲取被依賴方,而是被依賴方主動(dòng)傳遞給依賴方。
本文我們介紹 Go 語(yǔ)言怎么使用依賴注入。
2 、Go 語(yǔ)言使用依賴注入
在 Go 語(yǔ)言中,怎么使用依賴注入呢?我準(zhǔn)備以 clean arch 架構(gòu)的代碼講解。
推薦讀者朋友們先閱讀我之前寫的一遍文章 「Go 語(yǔ)言整潔架構(gòu)實(shí)踐」。
參照 Bob 大叔的一篇關(guān)于整潔架構(gòu)的文章 The Clean Architecture,我們分 4 個(gè)層級(jí):
- Models
- Repository
- Usecase
- Delivery
限于篇幅,本文主要介紹在 Go 語(yǔ)言中使用構(gòu)造函數(shù)的方式實(shí)現(xiàn)依賴注入,讀者朋友們可以在留言區(qū)分享其它實(shí)現(xiàn)方式。
示例代碼:
// 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 函數(shù)
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)
}
閱讀上面這段代碼,我們可以發(fā)現(xiàn) Repository 層依賴數(shù)據(jù)庫(kù)驅(qū)動(dòng) conn,Usecase 層依賴 Repository 層,Delivery 層依賴 Usecase 層。
以 Repository 層和 Usecase 層為例,我們可以發(fā)現(xiàn) Usecase 層通過構(gòu)造函數(shù) func NewTodoListUsecase(t models.TodoListRepository) models.TodoListRepository 將其依賴項(xiàng) models.TodoListRepository 以參數(shù)的形式傳遞過來,并將其放入 todoListUsecase 結(jié)構(gòu)體中。
所以,我們使用 Usecase 層的構(gòu)造函數(shù) NewTodoListUsecase 創(chuàng)建 Usecase 對(duì)象時(shí),需要先使用 Repository 層的構(gòu)造函數(shù) NewMysqlTodoListRepository 創(chuàng)建 Repository 對(duì)象,并將其以參數(shù)的形式傳遞給 Usecase 層的構(gòu)造函數(shù) NewTodoListUsecase。
通過依賴注入的方式,可以有效降低組件層級(jí)之間的耦合性,方便代碼的擴(kuò)展。比如示例代碼中 Repository 層的方法修改代碼,不會(huì)影響 Usecase 層的代碼。
3 、依賴注入工具
除了手寫依賴注入代碼,我們也可以使用依賴注入工具,開源社區(qū)有很多依賴注入工具,其中比較流行的主要有以下 3 個(gè)。
Google 開源的依賴注入工具 Wire[1],它是一個(gè)代碼生成工具,也就是說它是在編譯時(shí)自動(dòng)生成代碼。
另外 2 個(gè)依賴注入工具是在運(yùn)行時(shí)基于 Go 反射實(shí)現(xiàn),分別是 uber開源的依賴注入工具 Dig[2] 和 facebook 開源的依賴注入工具[3]。
讀者朋友們可以根據(jù)實(shí)際開發(fā)中的需求,選擇合適的工具。
4 、總結(jié)
讀者朋友們可能已經(jīng)發(fā)現(xiàn),依賴注入實(shí)際上就是面向?qū)ο笪宕笤瓌t之一,依賴倒置原則的實(shí)現(xiàn)方式。
我們可以在 Go 項(xiàng)目開發(fā)中,使用依賴注入的方式,降低組件層級(jí)之間的代碼耦合性,使代碼更方便擴(kuò)展。
參考資料
[1]Google 開源的依賴注入工具 Wire: https://github.com/google/wire
[2]uber開源的依賴注入工具 Dig: https://github.com/uber-go/dig
[3]facebook 開源的依賴注入工具: https://github.com/facebookarchive/inject