Go語言中interface語法與使用詳解
初識interface
Go語言的面向?qū)ο蟮闹R點時,發(fā)現(xiàn)它的面向?qū)ο竽芰θ?interface 撐著,而且它的 interface 還與我們以前知道的 interface 完全不同。故而整個過程不斷的思考為什么要如此設計?這樣設計給我們帶來了什么影響?
interface(接口)是golang最重要的特性之一,實現(xiàn)多態(tài)。Interface類型可以定義一組方法,但是這些不需要實現(xiàn)。并且interface不能包含任何變量。
基本語法
定義一個接口
type Person interface { // 聲明方法 method1(參數(shù)列表)返回值列表 method2(參數(shù)列表)返回值列表 }
實現(xiàn)一個接口
func (t 自定義類型)method1(參數(shù)列表)返回值列表 { //方法實現(xiàn) } func (t 自定義類型)method2(參數(shù)列表)返回值列表 { //方法實現(xiàn) }
小結(jié):
(1)接口里的所有方法都沒有方法體,即接口的方法都是沒有實現(xiàn)的方法。接口體現(xiàn)了程序設計的多態(tài)和高內(nèi)聚低耦合的思想。
(2)Go中的接口,不需要顯示的實現(xiàn)。只要一個變量,含有接口類型中的所有方法,那么這個變量就實現(xiàn)這個接口。因此,Go中沒有implement關鍵字樣。
(3)Go實現(xiàn)接口與方法有關,與接口本身叫什么名字沒有特別大的關系。變量需要實現(xiàn)接口所有的方法。
其他注意事項
(1)接口本身不能創(chuàng)建實例,但是可以指向一個實現(xiàn)了該接口的自定義類型的變量(實例)。
package main import "fmt" // Person 定義接口 type Person interface { GetName() string GetAge() uint32 } // Student 定義類型 type Student struct { Name string Age uint32 } func (s Student) GetName() string{ return s.Name } func (s Student) GetAge() uint32{ return s.Age } func main() { var student Student student.Age = 12 student.Name = "小明" var person Person person = student //接口執(zhí)行向student fmt.Printf("name:%s,age: %d\n", person.GetName(), person.GetAge()) }
(2)接口中所有的方法都沒有方法體,即都是沒有實現(xiàn)的方法。
(3)在Go中,一個自定義類型需要將某個接口的所有方法都實現(xiàn),我們說這個自定義類型實現(xiàn)了該接口。
(4)一個自定義類型只有實現(xiàn)了某個接口,才能將該自定義類型的實例(變量)賦給接口類型。
(5)只要是自定義數(shù)據(jù)類型就可以實現(xiàn)接口,不僅僅是結(jié)構(gòu)體類型。
(6)一個自定義類型可以實現(xiàn)多個接口。
(7)Go接口不能有任何變量。
(8)一個接口可以繼承多個別的接口,這時如果要實現(xiàn)這個接口必須實現(xiàn)它繼承的所有接口的方法。在低版本的Go編輯器中,一個接口繼承其他多個接口時,不允許繼承的接口有相同的方法名。比如A接口繼承B、C接口,B、C接口的方法名不能一樣。高版本的Go編輯器沒有相關問題。
(9)interface類型默認是一個指針(引用類型),如果沒有對interface初始化就使用,那么會輸出nil。
(10)空接口interface{}沒有任何方法,所以所有類型都實現(xiàn)了空接口,即我們可以把任何一個變量賦給空接口類型。
interface底層實現(xiàn)
Go的interface源碼在Golang源碼的runtime目錄中。
Go的interface是由兩種類型來實現(xiàn)的:iface和eface。
iface
iface是包含方法的interface,如:
type Person interface { Print() }
iface的源代碼是:
type iface struct { tab *itab data unsafe.Pointer }
iface具體結(jié)構(gòu)是:
itab是iface不同于eface的關鍵數(shù)據(jù)結(jié)構(gòu)。其包含兩部分:一部分是唯一確定包含該interface的具體結(jié)構(gòu)類型,一部分是指向具體方法集的指針。其具體結(jié)構(gòu)為:
屬性 itab的源代碼是:
type itab struct { inter *interfacetype //此屬性用于定位到具體interface _type *_type//此屬性用于定位到具體interface hash uint32// copy of _type.hash. Used for type switches. _ [4]byte fun[1]uintptr // variable sized. fun[0]==0 means _type does not implement inter. }
屬性interfacetype類似于_type,其作用就是interface的公共描述,類似的還有maptype、arraytype、chantype…其都是各個結(jié)構(gòu)的公共描述,可以理解為一種外在的表現(xiàn)信息。interfacetype源碼如下:
type interfacetype struct { typ _type pkgpath name mhdr []imethod } type imethod struct { name nameOff ityp typeOff }
iface的整體結(jié)構(gòu)為:
我們來看一個例子,對于含有方法的interface賦值后的內(nèi)部結(jié)構(gòu)是怎樣的呢?
package main import "fmt" // Person 定義接口 type Person interface { GetName() string GetAge() uint32 } // Student 定義類型 type Student struct { Name string Age uint32 } func (s Student) GetName() string{ return s.Name } func (s Student) GetAge() uint32{ return s.Age } func main() { var student Student student.Age = 12 student.Name = "小明" var person Person person = student fmt.Printf("name:%s,age: %d\n", person.GetName(), person.GetAge()) }
運行結(jié)果:
name:小明,age: 12
Process finished with the exit code 0
內(nèi)存分布示意圖:
eface
eface是不包含方法的interface,即空interface,如:
type Person interface { }
或者
var person interface{} = xxxx實體
侵入式與非侵入式的理解
侵入式
你的代碼里已經(jīng)嵌入了別的代碼,這些代碼可能是你引入過的框架,也可能是你通過接口繼承得來的,比如:java中的繼承,必須顯示的表明我要繼承那個接口,這樣你就可以擁有侵入代碼的一些功能。所以我們就稱這段代碼是侵入式代碼。
優(yōu)點:通過侵入代碼與你的代碼結(jié)合可以更好的利用侵入代碼提供給的功能。
缺點:框架外代碼就不能使用了,不利于代碼復用。依賴太多重構(gòu)代碼太痛苦了。
非侵入式
正好與侵入式相反,你的代碼沒有引入別的包或框架,完完全全是自主開發(fā)。比如go中的接口,不需要顯示的繼承接口,只需要實現(xiàn)接口的所有方法就叫實現(xiàn)了該接口,即便該接口刪掉了,也不會影響我,所有go語言的接口數(shù)非侵入式接口;再如Python所崇尚的鴨子類型。
優(yōu)點:代碼可復用,方便移植。非侵入式也體現(xiàn)了代碼的設計原則:高內(nèi)聚,低耦合。
缺點:無法復用框架提供的代碼和功能。
接下來看看java與go語言編程實現(xiàn)接口來理解侵入式與非侵入式的區(qū)別。
java語言實現(xiàn)
定義接口
public interface IPersonService { String getName(); Integer getAge(); }
實現(xiàn)接口的類
public class PersonService implements IPersonService{ @Override public String getName() { return "小明"; } @Override public Integer getAge() { return 12; } }
go語言實現(xiàn)
package main import "fmt" // Person 定義接口 type Person interface { GetName() string GetAge() uint32 } // Student 定義類型 type Student struct { Name string Age uint32 } func (s Student) GetName() string{ return s.Name } func (s Student) GetAge() uint32{ return s.Age } func main() { var student Student student.Age = 12 student.Name = "小明" var person Person person = student fmt.Printf("name:%s,age: %d\n", person.GetName(), person.GetAge()) }
通過上面的例子我們總結(jié)了以下問題:
- 侵入式通過 implements 把實現(xiàn)類與具體接口綁定起來了,因此有了強耦合;
- 假如修改了接口方法,則實現(xiàn)類方法必須改動;
- 假如類想再實現(xiàn)一個接口,實現(xiàn)類也必須進行改動;
- 后續(xù)實現(xiàn)此接口的類,必須了解相關的接口;
Go語言非侵入式的方式很好地解決了這幾個問題,只要實現(xiàn)了實現(xiàn)了與接口相同的方法,就實現(xiàn)了這個接口。隨著代碼量的增加,根本不需要的關心實現(xiàn)了哪些接口,不需要刻意去先定義接口再實現(xiàn)接口的固定模式,在原有類新增實現(xiàn)接口時,不需要更改類,做到低侵入式、低耦合開發(fā)的好處。
interface的應用場景
類型轉(zhuǎn)換
類型推斷可將接口變量還原為原始類型,或用來判斷是否實現(xiàn)了某個更具體的接口類型。
type data int func(d data)String()string{ return fmt.Sprintf("data:%d",d) } func main() { var d data=15 var x interface{} =d if n,ok:=x.(fmt.Stringer);ok{ // 轉(zhuǎn)換為更具體的接口類型 fmt.Println(n) } if d2,ok:=x.(data);ok{ // 轉(zhuǎn)換回原始類型 fmt.Println(d2) } e:=x.(error) // 錯誤:main.data is not error fmt.Println(e) }
輸出為:
data:15
data:15
panic:interface conversion:main.data is not error:missing method Error
但是此處會觸發(fā)panic,使用ok-idiom模式,即便轉(zhuǎn)換失敗也不會引發(fā)panic。還可用switch語句在多種類型間做出推斷匹配,這樣空接口就有更多發(fā)揮空間。
func main() { var x interface{} =func(x int)string{ return fmt.Sprintf("d:%d",x) } switch v:=x.(type) {// 局部變量v是類型轉(zhuǎn)換后的結(jié)果 case nil: println("nil") case*int: println(*v) case func(int)string: println(v(100)) case fmt.Stringer: fmt.Println(v) default: println("unknown") } }
輸出為:
d:100
實現(xiàn)多態(tài)功能
多態(tài)功能是interface實現(xiàn)的重要功能,也是Golang中的一大行為特色,其多態(tài)功能一般要結(jié)合Go method實現(xiàn),作為函數(shù)參數(shù)可以容易的實現(xiàn)多臺功能。
package main import "fmt" // notifier是一個定義了通知類行為的接口 type notifier interface { notify() } // 定義user及user.notify方法 type user struct { name string email string } func (u *user) notify() { fmt.Printf("Sending user email to %s<%s>\n", u.name, u.email) } // 定義admin及admin.notify方法 type admin struct { name string email string } func (a *admin) notify() { fmt.Printf("Sending admin email to %s<%s>\n", a.name, a.email) } func main() { // 創(chuàng)建一個user值并傳給sendNotification bill := user{"Bill", "bill@email.com"} sendNotification(&bill) // 創(chuàng)建一個admin值并傳給sendNotification lisa := admin{"Lisa", "lisa@email.com"} sendNotification(&lisa) } // sendNotification接受一個實現(xiàn)了notifier接口的值 // 并發(fā)送通知 func sendNotification(n notifier) { n.notify() }
上述代碼中實現(xiàn)了一個多態(tài)的例子,函數(shù)sendNotification接受一個實現(xiàn)了notifier接口的值作為參數(shù)。既然任意一個實體類型都能實現(xiàn)該接口,那么這個函數(shù)可以針對任意實體類型的值來執(zhí)行notify方法,調(diào)用notify時,會根據(jù)對象的實際定義來實現(xiàn)不同的行為,從而實現(xiàn)多態(tài)行為。
補充:interface 與 nil 的比較
引用公司內(nèi)部同事的討論議題,覺得之前自己也沒有理解明白,為此,單獨羅列出來,例子是最好的說明,如下
package main import ( "fmt" "reflect" ) type State struct{} func testnil1(a, b interface{}) bool { return a == b } func testnil2(a *State, b interface{}) bool { return a == b } func testnil3(a interface{}) bool { return a == nil } func testnil4(a *State) bool { return a == nil } func testnil5(a interface{}) bool { v := reflect.ValueOf(a) return !v.IsValid() || v.IsNil() } func main() { var a *State fmt.Println(testnil1(a, nil)) fmt.Println(testnil2(a, nil)) fmt.Println(testnil3(a)) fmt.Println(testnil4(a)) fmt.Println(testnil5(a)) }
返回結(jié)果如下
false
false
false
true
true
為啥呢?
一個interface{}類型的變量包含了2個指針,一個指針指向值的類型,另外一個指針指向?qū)嶋H的值 對一個interface{}類型的nil變量來說,它的兩個指針都是0;但是var a *State傳進去后,指向的類型的指針不為0了,因為有類型了, 所以比較為false。 interface 類型比較, 要是 兩個指針都相等, 才能相等。
總結(jié)
到此這篇關于Go語言中interface語法與使用詳解的文章就介紹到這了,更多相關Go語言interface詳解內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務器上建立鏡像,否則將依法追究法律責任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學習參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。