golang中interface的一些語法缺陷的改進
GO語言有一個獨門祕技:interface,是大師們對OOP的經典詮釋,是對傳統OOP思維的一個巧妙顛覆。
既優雅地實現執行時多型,又不需要像C++那樣顯式申明,完美的解除了型別實現跟介面呼叫之間的耦合。
Go語言的主要設計者之一Russ Cos曾經說過,如果只能選擇一個Go語言的特性移植到其他語言中,他會選擇介面。可見介面在GO中的地位,及其對GO這門語言所帶來的活力。
Russ Cos: interface https://research.swtch.com/interfaces
Ian Lance Taylor: go interface https://www.airs.com/blog/archives/277
然而,由於GO的interface定義與具體實現型別之間不需要顯式申明,只需要實現類實現interface定義的所有method即可賦值。
然而這種寬鬆的約定,卻帶來一些邏輯上的麻煩。
這裡有個例子:https://github.com/vipally/glab/blob/master/lab12/walk_test.go
有兩個包filepath和pet都定義了一個Walker介面,並實現了相應的實現型別。
但是巧的是這兩個Walker介面的定義都是一樣的
type Walker interface {
Walk()
}
但是顯然,pet.Walker是想實現所有pet的“走路”行為
而filepath.Walker是想實現目錄的“遍歷”功能
然而,雖然兩個介面的定義是一樣的,但是期望的行為應該是不一樣的。
從邏輯上說,用filepath.Walker呼叫pet.Dog.Walk()應該是一個錯誤的行為,然而從GO的語法上看,這種操作居然是合法的:
var petWalker pet.Walker var filepathWalker filepath.Walker println("call by pet.Walker:") petWalker = &pet.Dog{} petWalker.Walk() petWalker = &pet.Cat{} petWalker.Walk() petWalker = &filepath.FilePath{} petWalker.Walk() println("\ncall by filepath.Walker:") filepathWalker = &pet.Dog{} filepathWalker.Walk() filepathWalker = &pet.Cat{} filepathWalker.Walk() filepathWalker = &filepath.FilePath{} filepathWalker.Walk() println("\nThe strange is that pet.Warker and filepath.Walker has the same signiture but they are not the same one.") println("But Go treat them as the same one.")
從語法上修復這個bug的方法如下:
implements pet.Walker{
*pet.Dog
*pet.Cat
}
implements *filepath.FilePath{
filepath.Walker
filepath.Reader
}
通過顯式的申明 實現並可以使用介面的具體型別,告訴編譯器什麼樣的介面賦值,是被允許的。
這種外部申明的語法,仍然沒有破壞GO interface非侵入式的設計,無須對interface和實現型別做任何修改。
這樣,下面的介面賦值將被編譯器判斷為非法
filepathWalker = &pet.Dog{}
另外,這種顯式申明還有一個好處,就是可以明確通過查詢引用的方法,找出實現了某個介面的所有實現型別。
起到一個建立介面定義與實現型別關係的書面說明,
不會像現在一樣,通過interface名字,只能找到interface的物件,卻沒法找出所有實現並賦值給該介面的所有具體型別的資訊。
參考連結:
Russ Coss: interface https://research.swtch.com/interfaces
Ian Lance Taylor: go interface https://www.airs.com/blog/archives/277
confused interface: https://github.com/vipally/glab/blob/master/lab12/walk_test.go