這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
1.其他語言的介面
Go語言的介面並不是其他語言(C++、Java、C#等)中所提供的介面概念。
在Go語言出現之前,介面主要作為不同組件之間的契約存在。對契約的實現是強制的,你
必須聲明你的確實現了該介面。為了實現一個介面,你需要從該介面繼承:
interface IFoo { void Bar();}class Foo implements IFoo { // Java文法 // ...}
即使另外有一個介面IFoo2實現了與IFoo有完全一樣的介面方法甚至名字也叫IFoo只不過位
於不同的名字空間(包名)下,編譯器也會認為上面的類Foo只實現了IFoo而沒有實現IFoo2介面。
這類介面我們稱為侵入式介面。“侵入式”的主要表現在於實作類別需要明確聲明自己實現了
某個介面。這種強制性的介面繼承是物件導向編程思想發展過程中一個遭受相當多置疑的特性。
2.非侵入式介面
在Go語言中,一個類只需要實現了介面要求的所有函數,我們就說這個類實現了該介面,
例如:
type File struct { // ...}func (f *File) Read(buf []byte) (n int, err error)func (f *File) Write(buf []byte) (n int, err error)func (f *File) Seek(off int64, whence int) (pos int64, err error)func (f *File) Close() error
這裡我們定義了一個File類並實現有Read()、Write()、Seek()、Close()等方法。設
想我們有如下介面:
type IFile interface { Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error) Seek(off int64, whence int) (pos int64, err error) Close() error}type IReader interface { Read(buf []byte) (n int, err error)}type IWriter interface { Write(buf []byte) (n int, err error)}type ICloser interface { Close() error}
儘管File類並沒有從這些介面繼承,甚至可以不知道這些介面的存在,只要File類
有包含這些介面的所有方法,就可以如下賦值使用.
var file1 IFile = new(File)var file2 IReader = new(File)var file3 IWriter = new(File)var file4 ICloser = new(File)
是不是很強大,最大的特點就是不需要明確(顯式)聲明自己實現了某個介面.
Go語言的非侵入式介面,看似只是做了很小的文法調整,實則影響深遠。
其一,Go語言的標準庫,再也不需要繪製類庫的繼承樹圖.
在Go中,類的繼承樹並無意義,你只需要知道這個類實現了哪些方法,每個方法是啥含義
就足夠了。
其二,實作類別的時候,只需要關心自己應該提供哪些方法,不用再糾結介面需要拆得多細才
合理。介面由使用方按需定義,而不用事前規劃。
其三,不用為了實現一個介面而匯入一個包,因為多引用一個外部的包,就意味著更多的耦
合。介面由使用方按自身需求來定義,使用方無需關心是否有其他模組定義過類似的介面。
3.介面的賦值
介面賦值在Go語言中分為如下兩種情況:
1)將對象執行個體賦值給介面;
2)將一個介面賦值給另一個介面。
先討論將某種類型的對象執行個體賦值給介面,這要求該對象執行個體實現了介面要求的所有方法,
例如之前我們作過一個 Integer 類型,如下:
type Integer intfunc (a Integer) Less(b Integer) bool { return a < b}func (a *Integer) Add(b Integer) { *a += b}
相應地,我們定義介面 LessAdder ,如下:
type LessAdder interface { Less(b Integer) bool Add(b Integer)}
現在有個問題:假設我們定義一個Integer類型的對象執行個體,怎麼將其賦值給LessAdder
介面呢?應該用下面的語句(1),還是語句(2)呢?
var a Integer = 1var b LessAdder = &a ...(1)var b LessAdder = a ... (2)
答案是應該用語句(1)。原因在於,Go語言可以根據下面的函數:
func (a Integer) Less(b Integer) bool
自動產生一個新的Less()方法:
func (a *Integer) Less(b Integer) bool { return (*a).Less(b)}
這樣,類型*Integer就既存在Less()方法,也存在Add()方法,滿足LessAdder介面的所有方法。
為了進一步證明以上的推理,我們不妨再定義一個Lesser介面,如下:
type Lesser interface { Less(b Integer) bool}
然後定義一個Integer類型的對象執行個體,將其賦值給Lesser介面:
var a Integer = 1var b1 Lesser = &a ...//(1)var b2 Lesser = a ... //(2)
正如我們所料的那樣,語句(1)和語句(2)均可以編譯通過。
我們再來討論另一種情形:
將一個介面賦值給另一個介面。在Go語言中,只要兩個介面擁
有相同的方法列表(次序不同不要緊),那麼它們就是等同的,可以相互賦值。
例如:
package onetype ReadWriter interface { Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error)}
第二個介面位於另一個包中:
package twotype IStream interface { Write(buf []byte) (n int, err error) Read(buf []byte) (n int, err error)}
這裡我們定義了兩個介面,一個叫 one.ReadWriter,一個叫two.Istream,兩者都定義
了Read()、Write()方法,只是定義次序相反,one.ReadWriter先定義了Read()再定義了
Write(),而two.IStream反之。
在Go語言中,這兩個介面實際上並無區別,因為:
1)任何實現了one.ReadWriter介面的類,均實現了two.IStream;
2)任何實現了one.ReadWriter介面的類,均實現了two.IStream;
3)在任何地方使用one.ReadWriter介面與使用two.IStream並無差異。
以下這些代碼均可編譯通過:
var file1 two.IStream = new(File)var file2 one.ReadWriter = file1var file3 two.IStream = file2
介面賦值並不要求兩個介面必須等價,如果介面A的方法列表是介面B的方法列表的子集,
那麼介面B可以賦值給介面A。例如,假設我們有Writer介面:
type Writer interface { Write(buf []byte) (n int, err error)}
就可以將上面的one.ReadWriter和two.IStream介面的執行個體賦值給Writer介面:
var file1 two.IStream = new(File)var file4 Writer = file1 //賦值給子集介面
但是反過來並不成立:
var file1 Writer = new(File)var file5 two.IStream = file1 // 編譯不能通過
這段代碼無法編譯通過,原因是顯然的:file1並沒有Read()方法。
4.介面查詢
有辦法判斷Writer介面是否可以轉換為two.IStream介面呢?
有,那就是我們即將討論的介面查詢文法,代碼如下:
var file1 Writer = ...if file5, ok := file1.(two.IStream); ok { ...}
這個if語句檢查file1介面指向的對象執行個體是否實現了two.IStream介面,如果實現了,則執
行特定的代碼。
類似的java文法中instanceof,比如查詢一個對象的類型是否繼承自某個類型(基類查詢),
或者是否實現了某個介面(介面派生查詢),但是它們的動態查詢與Go的動態查詢很不一樣。
5.類型查詢
在Go語言中,還可以更加直截了當地詢問介面指向的對象執行個體的類型,例如:
var v1 interface{} = ... switch v := v1.(type) { case int: // 現在v的類型是int case string: // 現在v的類型是string ...}
就像現實生活中物種多得數不清一樣,語言中的類型也多得數不清,所以類型查詢並不經常
使用。它更多是個補充,需要配合介面查詢使用,例如:
package mainimport ( "fmt")type Stringer interface { //定義介面 String() string}type MyStringer struct{ //定義結構體}func (*MyStringer) String()string{ //實現介面的方法 return "this is a method implement from Stringer"}func main(){ Println(123) Println("mChenys") var myStringer Stringer = new(MyStringer) Println(myStringer)}func Println(args ...interface{}) {//不定參數 for _, arg := range args { switch v := arg.(type) {//判斷類型 case int: fmt.Println("現在",v,"的類型是int") case string: fmt.Println("現在",v,"的類型是string") default: if v, ok := arg.(Stringer); ok { // 現在v的類型是Stringer val := v.String() fmt.Println(val) } else { fmt.Println("其它類型") } } }}
//輸出結果:
現在 123 的類型是int
現在 mChenys 的類型是string
this is a method implement from Stringer
6.介面組合(嵌套)
像之前介紹的類型(結構體)組合一樣,Go語言同樣支援介面組合,例如:
// ReadWriter介面將基本的Read和Write介面組合起來type ReadWriter interface { Reader Writer}
這個介面組合了Reader和Writer兩個介面,它完全等同於如下寫法:
type ReadWriter interface { Read(p []byte) (n int, err error)//Reader的方法 Write(p []byte) (n int, err error)//Writer的方法}
因為這兩種寫法的表意完全相同:ReadWriter介面既能做Reader介面的所有事情,又能做
Writer介面的所有事情.在Go語言套件中,還有眾多類似的組合介面,比如ReadWriteCloser、
ReadWriteSeeker、ReadSeeker和WriteCloser等。
可以認為介面組合是類型匿名組合的一個特定情境,只不過介面只包含方法,而不包含任何
成員變數。
7.Any類型
由於Go語言中任何對象執行個體都滿足空介面interface{},所以interface{}看起來像是可
以指向任何對象的Any類型,如下:
var v1 interface{} = 1 //將int類型賦值給interface{}var v2 interface{} = "abc" //將string類型賦值給interface{}var v3 interface{} = &v2 //將*interface{}類型賦值給interface{}var v4 interface{} = struct{ X int }{1}var v5 interface{} = &struct{ X int }{1}
當函數可以接受任意的對象執行個體時,我們會將其聲明為interface{},最典型的例子是標
准庫fmt中PrintXXX系列的函數,例如:
func Printf(fmt string, args ...interface{})func Println(args ...interface{})...