Go編程技巧之「延後抽象」
魯迅曾經說過,複雜的問題都能用增加一層抽象來解決。(魯迅: 我特么沒說過!)
在實際工程中,為了解耦,可能需要定義大量的介面。
如在用戶管理系統中,可能含有一個像根據用戶名檢索用戶的服務 UserSearcher:
type UserSearcher interface{ Search(name string)([]*User,error)}
一般情況下在項目初期,只有一個基於資料庫的實現——*DBUserSearcher。
type DBUserSearcher struct{}func (*DBUserSearcher) Search(name string)(users []*User,err error){ return }
如此這樣,如果有幾十個服務,可能就要定義幾十個介面,畢竟隨時可能替換具體實現,避免改動對整個系統造成的衝擊。然而百分之九十的情況之下,像這些的介面在軟體整個生命中都只存在一種實現,代碼量的膨脹顯然帶來的收益很小。
而在項目剛啟動的時候,介面的定義往往改動很頻繁,這時候不僅僅需要在介面定義上修改,還要去介面實現的類型上進行修改,這樣就會非常痛苦,很不靈活。
如果這些困擾也曾讓您煩惱過,那麼接下來,我介紹的這一種我稱之為「延後抽象」的技巧就能夠幫助您減輕很大一部分痛苦。
在Go的1.9版本以上,利用類型別名的特性,結合golang介面的鴨子類型特點:
先將本要定義成介面的抽象類型,直接定義為具體實現類型的別名,在將來需要擴展抽象類型具體實現的時候,再將抽象類型定義為介面。
比如:
在第一個版本代碼里,我們簡單的將UserSearcher取為*DBUserSearcher的別名。
type UserSearcher = *DBUserSearcher
這個時候其他模塊使用的是UserSearcher這一類型,而不是『具體』的*DBUserSearcher類型。
而在將來某次迭代中,為了提升檢索效率,可能選擇性的使用基於Elasticsearch的檢索。
我們在此時再將UserSearcher 改為介面定義
type UserSearcher interface{ Search(name string)([]*User,error)}
這時,由於Go介面的鴨子類型特性,*DBUserSearcher自然仍然是UserSearcher類型。
其餘代碼不變,只需要增加一個基於Elasticsearch調用的UserSearcher實現即可。
type ESUserSearcher struct{}func (*ESUserSearcher) Search(name string)(users []*User,err error){ return }
這種通過這種結合了鴨子類型和類型別名的「延遲抽象」的辦法,由於避免了大量無用的介面定義,就能在項目剛開發階段省下非常多不必要的代碼,同時也不會降低代碼的可維護性。
推薦閱讀: