Go 使用依賴注入設(shè)計(jì)更好的代碼
點(diǎn)擊上方藍(lán)色“Golang來(lái)啦”關(guān)注我喲
加個(gè)“星標(biāo)”,天天 15 分鐘,掌握?Go 語(yǔ)言
via:
https://medium.com/effective-development/building-better-software-in-go-with-di-faf8301a9f84
作者:Sergey Suslov
四哥水平有限,如有翻譯或理解錯(cuò)誤,煩請(qǐng)幫忙指出,感謝!
原文如下:
在這篇文章中,我將簡(jiǎn)單介紹下什么是 SOLID 理論、什么是依賴注入(DI)以及如何運(yùn)用它們?nèi)ゾ帉懜玫拇a。我們會(huì)演示很多的的代碼示例。本文的重點(diǎn)是展示如何使用 DI 和 SOLID 編寫更具測(cè)試性和高可用的代碼。

什么是 SOLID?
SOLID 是 Robert Martin 在他的書中提出的一組原則:
單一職責(zé)原則(S)
開(kāi)閉原則(O)
里氏替換原則(L)
接口分離原則(I)
依賴倒置原則(D)
上面這些原則可以幫助我們編寫更好的代碼,今天我們只會(huì)介紹第一條和最后一條原則。
依賴倒置原則(DIP)
這條原則是說(shuō)軟件模塊應(yīng)該依賴于抽象而不依賴于具體的實(shí)現(xiàn),這樣才能設(shè)計(jì)更具靈活性的系統(tǒng)。
一起來(lái)看下下面這個(gè)例子:

在這個(gè)圖上,可以看到有兩個(gè)類 ClientService 和 PostgresClientRepository。我們假設(shè) ClientService 包含業(yè)務(wù)邏輯,通過(guò) PostgresClientRepository 類可以操作 PostgreSQL。ClientService 依賴于 PostgresClientRepository。
代碼如下:
type?PostgresClientRepository?struct?{
}
func?(c?PostgresClientRepository)?Do()?{
???log.Println("Done")
}
type?ClientService?struct?{
???clientRepository?PostgresClientRepository
}
上面這種代碼設(shè)計(jì)違背了依賴倒置原則(DIP),因?yàn)?ClientService 依賴于具體的實(shí)現(xiàn)。這種關(guān)系一定程度上限制了我們?nèi)バ薷?PostgresClientRepository 類,降低了代碼靈活性。
基于依賴倒置原則(DIP),解決辦法如下:

現(xiàn)在,這兩個(gè)類都依賴于 ClientRepository 接口,ClientService 依賴于穩(wěn)定的接口 ClientRepository,它可以基于該接口實(shí)現(xiàn)自己想要的東西。PostgresClientRepository 也依賴于該接口,并且可以保持其靈活性。
修改之后代碼如下:
type?ClientRepository?interface?{
???Do()
}
type?PostgresClientRepository?struct?{
}
func?(c?PostgresClientRepository)?Do()?{
???log.Println("Done")
}
type?ClientService?struct?{
???clientRepository?ClientRepository
}
單一職責(zé)原則(SRP)
這個(gè)原則是說(shuō)一個(gè)模塊只有一個(gè)理由去修改,換句話說(shuō),一個(gè)模塊只需要承擔(dān)唯一的職責(zé)。(ps:各種邏輯處理不能冗雜在一個(gè)函數(shù)里面,一個(gè)函數(shù)完成一項(xiàng)功能即可)。
這樣做是為了實(shí)現(xiàn):
測(cè)試更方便;
減少改動(dòng)代碼之后模塊之間相互影響;
讓我給你舉個(gè)例子,這個(gè)原則是如何使生活變得更美好的。讓我們假設(shè)現(xiàn)在有一個(gè)接口和它的實(shí)現(xiàn),ClientRepository 是接口,ClientRepositoryImpl是它的實(shí)現(xiàn)。
type?ClientRepository?interface?{
???Do()
}
type?ClientRepositoryImpl?struct?{
}
func?(c?ClientRepositoryImpl)?Do()?{
???log.Println("Done")
}
現(xiàn)在來(lái)看下基于 ClientRepository 及其構(gòu)造函數(shù)提供的服務(wù)。
type?ClientService?struct?{
???clientRepository?ClientRepository
}
func?NewClientService()?*ClientService?{
???return?&ClientService{clientRepository:?ClientRepositoryImpl{}}
}
這個(gè)構(gòu)造函數(shù)違背了單一職責(zé)原則(SRP),它不應(yīng)該負(fù)責(zé)創(chuàng)建 ClientRepository。作為 ClientService 的一部分,這個(gè)構(gòu)造函數(shù)決定了 ClientRepository 的具體實(shí)現(xiàn)。
這非常糟糕,作為開(kāi)發(fā)人員,如果我們想換一種實(shí)現(xiàn)方式就必須重寫構(gòu)造函數(shù);另外,在測(cè)試的時(shí)候也如法模擬 clientRepository。
這個(gè)問(wèn)題的解決辦法很明顯:
type?ClientService?struct?{
???clientRepository?ClientRepository
}
func?NewClientService(clientRepository?ClientRepository)?*ClientService?{
???return?&ClientService{clientRepository}
}
但是,這個(gè)解決辦法有個(gè)新的問(wèn)題,想要?jiǎng)?chuàng)建 ClientService,必須自己先創(chuàng)建 ClientRepository 并將其作為參數(shù)傳遞給構(gòu)造函數(shù)。
這就是依賴注入(DI)可以提供極大幫助的地方。
依賴注入機(jī)制
依賴注入是一種對(duì)象接收其所依賴的對(duì)象的技術(shù)。
Uber dig 就是一個(gè)強(qiáng)大易用的 DI 工具包,并且提供了很多好的示例。
這個(gè)包提供了兩個(gè)主要的函數(shù),第一個(gè)就是 Provide,允許我們定義自己的依賴項(xiàng)。
通過(guò) Provide 方法將不同類型的構(gòu)造函數(shù)添加到容器里面。構(gòu)造函數(shù)只需要將其添加為函數(shù)參數(shù)就可以聲明對(duì)另一類型的依賴。類型的依賴關(guān)系可以在添加類型之前或之后添加到圖中。
提供 ClientService
讓我們嘗試解決上一節(jié)中創(chuàng)建 ClientService 遇到的問(wèn)題。
下面的代碼提供 ClientRepositoryImpl 作為 ClientRepository 實(shí)現(xiàn):
type?ClientRepository?interface?{
???Do()
}
type?ClientRepositoryImpl?struct?{
}
func?(c?ClientRepositoryImpl)?Do()?{
???log.Println("Done")
}
var?C?*dig.Container
func?main()?{
???C?=?dig.New()
???C.Provide(func()?ClientRepository?{
??????return?&ClientRepositoryImpl{}
???})
}
使用 dig 的好處是能自動(dòng)地為構(gòu)造函數(shù)提供所需要的依賴。在這個(gè)例子中,可能像下面這樣提供 ClientSevice:
type?ClientService?struct?{
???clientRepository?ClientRepository
}
func?NewClientService(clientRepository?ClientRepository)?*ClientService?{
???return?&ClientService{clientRepository}
}
var?C?*dig.Container
func?main()?{
???C?=?dig.New()
???C.Provide(func()?ClientRepository?{
??????return?&ClientRepositoryImpl{}
???})
???C.Provide(NewClientService)
}
從現(xiàn)在開(kāi)始,就可以在程序的任務(wù)位置,像下面這樣從 dig 容器中獲取 ClientService。
var?clientService?*ClientService
C.Invoke(func(s?*ClientService)?{
???clientService?=?s
})
使用 Dig 的好處
不依賴具體的實(shí)現(xiàn),使得代碼更靈活且有利于單元測(cè)試;
開(kāi)發(fā)人員無(wú)需費(fèi)心創(chuàng)建所有的依賴項(xiàng);
所有的實(shí)現(xiàn)可以集中在一處位置進(jìn)行控制;
總結(jié)
遵循 SOLID 原則使得代碼更加靈活、易于測(cè)試和使用。依賴注入可以幫助你構(gòu)建對(duì)象,并可以減少冗余代碼。
推薦閱讀
站長(zhǎng) polarisxu
自己的原創(chuàng)文章
不限于 Go 技術(shù)
職場(chǎng)和創(chuàng)業(yè)經(jīng)驗(yàn)
Go語(yǔ)言中文網(wǎng)
每天為你
分享 Go 知識(shí)
Go愛(ài)好者值得關(guān)注
