依賴注入(DI)是一種解耦組件之間依賴關系的設計模式。在需要的時候,不同組件之間可以通過一個統一的界面獲取其它組件中的對象和狀態。Go語言的接口設計,避免了很多需要使用第三方依賴注入框架的情況(比如Java,等等)。我們的注入方案只提供非常少的類似Dager或Guice中的注入方案,而專注於盡量避免手動去配置對象和組件之間的依賴關系。因為,我們認為如果在Go代碼庫中,注入能夠更加容易理解,就根本沒有必要那樣。
在Go中實現注入只需要這幾個簡單的步驟:
先從一個一致的、崇高的目標開始,我們需要一些如Mongo、Memcache等服務的全局連接對象。大致是這樣的:
var MongoService mongo.Service
func InitMongoService(url string) {
MongoService = ...
}
func GetApp(id uint64) *App {
a := new(App)
MongoService.Session().Find(..).One(a)
return a
}
通常 main() 函數會調用配置在flags或configuration文件中如 InitMongoService 這樣的各種初始化函數。這時,像 GetApp 這樣的函數就可以使用這些服務和連接了。當然,有時候我們會忘記初始化全局變量,被 nil 引發panic。
雖然在創建全局變量的時候共享資源讓它們(至少)有兩個缺點:
首先,因為組件的依賴關系不明確,所以代碼是很難寫的;
其次,你很難去測試你寫的代碼,在並行條件下更是幾乎不可能。
盡管測試是非常快的(我們希望確保一直很快),但是能夠在並行環境下測試才是最重要的。使用全局連接對象時,後台服務無法在並發條件下測試出相同的數據。
為了清除全局變量,我們先從一個通用模式開始。我們的組件現在顯示依賴,我們將,一個Mongo服務,或者一個緩存服務。大致來講,我們上面那個幼稚的例子現在看起來應當是這樣的:
type AppLoader struct {
MongoService mongo.Service
}
func (l *AppLoader) Get(id uint64) *App {
a := new(App)
l.MongoService.Session().Find(..).One(a)
return a
}
許多引用全局變量的函數現在變成了結構體中存儲了它們的依賴。
真棒!在main()方法中,我們用一系列的構造代替了全局變量和函數,解決了我們之前遇到的問題。但是... 一看main()函數就知道了,太雜亂無章了。
一開始就這麼亂了:
func main() {
mongoURL := flag.String(...)
mongoService := mongo.NewService(mongoURL)
cacheService := cache.NewService(...)
appLoader := &AppLoader{
MongoService: mongoService,
}
handlerOne := &HandlerOne{
AppLoader: appLoader,
}
handlerTwo := &HandlerTwo{
AppLoader: appLoader,
CacheService: cacheService,
}
rootHandler := &RootHandler{
HandlerOne: handlerOne,
HandlerTwo: handlerTwo,
}
...
}
如果一直這樣寫下去,main()函數的方法體將會被被大量的代碼占據。而這些代碼僅僅只是做了兩件很普通的事情:分配內存空間、裝配對象和組件關系。如果我們有非常多的二進制代碼和庫需要引用,我們就需要一遍又一遍的寫這些無聊的代碼。這裡特別需要注意的是,不要被nil引發panic。比如我們忘記把CacheService傳遞給HandlerTwo,然後就引發了一個運行時panic。我們試圖構造一個方法,但是卻變得有些失控。還需要寫一大堆的代碼手動檢查nil。因為必須手動裝配對象並確保運行正常,我們的開發對此非常惱火。測試人員甚至還需要自己裝配對象、構建關系,顯然他們不會在main()函數中共用這些代碼。所以測試代碼也變得越來越繁雜、冗余,卻還是經常找不出實際問題。簡而言之,我們解決了一個問題,卻產生了另一種問題。
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-09/106330p2.htm