標籤:
Go語言中隱式介面的衝突問題
Go語言中採用的是隱式介面, 只要滿足的介面的定義, 就可以當作介面使用.
比如內建的 error
介面:
type error struct {Error() string}
隱式介面的好處有很多. 但我個人覺得最主要的一點就是不需要再去畫祖宗八代的繼承關係圖了(松耦合).
但是隱式介面會帶來衝突問題.
簡單來說, 我也想定義一個自己的 MyError
介面, 裡面也有一個 Error() string
方法:
type MyError struct {Error() string}
但是我希望 MyError
介面 和 error
介面 是不同的類型 (不能相互轉換).
當然, 在 Go語言中 MyError
介面 和 error
介面 是等價的, 禁止 相互轉換 比較困難.
我們一般可以在 MyError
介面 中增加一個唯一的空方法 來迴避這個問題:
type MyError struct {Error() stringAssertMyError()}
方法 AssertMyError
只是為了區別 error
介面, 沒有其他用處.
這是 Protobuf 中 proto.Message 採用的方法:
// Message is implemented by generated protocol buffer messages.type Message interface {Reset()String() stringProtoMessage()}
產生的每個 Message
類型有個特殊的 ProtoMessage
空方法, 特別對應 proto.Message
介面.
當然, 如果有另一個介面剛好也有 ProtoMessage
方法, 還是有衝突的危險.
極端的做法是隨機產生一個 特別的 方法名, 比如用 UUID 做唯一名字.
但是, 公開的名字依然有被別人惡意覆蓋的危險(實際中不大可能).
更嚴格的做法是將這個用於區別介面的方法名定義為私人的方法. 比如 testing.TB
:
type TB interface {Error(args ...interface{})Errorf(format string, args ...interface{})Fail()FailNow()Failed() boolFatal(args ...interface{})Fatalf(format string, args ...interface{})Log(args ...interface{})Logf(format string, args ...interface{})Skip(args ...interface{})SkipNow()Skipf(format string, args ...interface{})Skipped() bool// A private method to prevent users implementing the// interface and so future additions to it will not// violate Go 1 compatibility.private()}
private
不僅僅是私人方法, 而且必須是 testing
包內部定義的 private()
方法的類型才能匹配這個介面!
因此 testing.TB
介面是全域唯一的, 不會出現等價可互換的介面.
現在 testing.TB
保證了介面的唯一性, 但是如何在外部實現 這個介面呢(private()
是testing
包內部定義的)?
我們可以從 testing.TB
介面繼承這個 private()
方法:
package mainimport ("fmt""testing")type TB struct {testing.TB}func (p *TB) Fatal(args ...interface{}) {fmt.Println("TB.Fatal disabled!")}func main() {var tb testing.TB = new(TB)tb.Fatal("Hello, playground")}
play 地址: http://play.golang.org/p/tFB0fLwq9q
上面的代碼類比了顯式介面, 而且 testing.TB
介面永遠不用擔心有衝突的危險.
當然, 上面的代碼有過度提示的問題, 這和Go語言簡單的編程哲學是矛盾的.
Go語言中隱式介面的衝突問題