最近在對之前的代碼做重構(gòu),從之前的 MVC 結(jié)構(gòu)切換到 Clean Arch 的結(jié)構(gòu),但是在切換的時候關(guān)于代碼風(fēng)格出現(xiàn)了一些困惑。
在下面的代碼中 repository 是存儲庫,主要用于封裝數(shù)據(jù)庫查詢或者是第三方微服務(wù)的調(diào)用,它實現(xiàn)了 domain.IAzRepository 接口,其他層的代碼都只依賴這個接口而不依賴具體的實現(xiàn)
三種代碼風(fēng)格
風(fēng)格一
在 Go 中我們常常“返回實現(xiàn)(struct),依賴接口”,其實就是在函數(shù)返回的時候我們返回一個具體的實現(xiàn),函數(shù)的參數(shù)或者是 Struct 的成員部分我們依賴接口,這個風(fēng)格看起來是違背了這個原則的
// repository 存儲庫
type repository struct {
db *gorm.DB
}
// NewAZRepository NewAZRepository
func NewAZRepository(db *gorm.DB) domain.IAzRepository {
return &repository{db: db}
}
風(fēng)格二
這個風(fēng)格返回了實現(xiàn),并且由于并沒有導(dǎo)出看起來也具有封裝的特性,但是如果你運行 golint 你就會發(fā)現(xiàn)會拋出錯誤,因為這么寫,會導(dǎo)致我們用導(dǎo)出的方法將沒有導(dǎo)出 struct 給暴露了出去
// repository 存儲庫
type repository struct {
db *gorm.DB
}
// NewAZRepository NewAZRepository
func NewAZRepository(db *gorm.DB) *repository {
return &repository{db: db}
}
風(fēng)格三
這個寫法的主要問題是,由于 Repository 被導(dǎo)出,所以在外部其他的包中就可以直接通過 &Repository{} 進行初始化,這樣初始化之后使用就會導(dǎo)致 panic,因為成員函數(shù)是一個 nil 指針
// Repository 存儲庫
type Repository struct {
db *gorm.DB
}
// NewAZRepository NewAZRepository
func NewAZRepository(db *gorm.DB) *Repository {
return &Repository{db: db}
}
選擇
選擇總是困難的,帶著這個問題我咨詢了同組的同事還有好幾個 Go 語言交流群的同學(xué),其中大部分都會選擇風(fēng)格三,小部分會選擇風(fēng)格一,風(fēng)格二幾乎沒有人選擇。最后我選什么呢?
最后我的選擇是風(fēng)格一,這是針對場景來的,因為我們的這個包其實不希望其他包直接依賴實現(xiàn),因為后續(xù)有可能隨著發(fā)展被單獨拆分成一個微服務(wù)或者是需要更換存儲庫,如果外部有包直接依賴 repository 會導(dǎo)致后續(xù)的重構(gòu)比較困難
除此之外,我們在其他地方一般還是會選擇風(fēng)格三,因為結(jié)構(gòu)體名不導(dǎo)出,外部其實沒有比較好的辦法進行初始化,例如想要 var r Repository ,至于前面提到的直接字面量初始化的問題,我們可以通過統(tǒng)一代碼風(fēng)格解決。
在 外部包 中除了用于參數(shù)傳遞的 Option 結(jié)構(gòu)之外,其余的不允許直接通過 &XXX{} 的方式進行初始化