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