溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go語言中interface語法與使用實(shí)例分析

發(fā)布時間:2022-07-15 10:08:03 來源:億速云 閱讀:123 作者:iii 欄目:開發(fā)技術(shù)

這篇“Go語言中interface語法與使用實(shí)例分析”文章的知識點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Go語言中interface語法與使用實(shí)例分析”文章吧。

    初識interface

    Go語言的面向?qū)ο蟮闹R點(diǎn)時,發(fā)現(xiàn)它的面向?qū)ο竽芰θ?interface 撐著,而且它的 interface 還與我們以前知道的 interface 完全不同。故而整個過程不斷的思考為什么要如此設(shè)計?這樣設(shè)計給我們帶來了什么影響?

    interface(接口)是golang最重要的特性之一,實(shí)現(xiàn)多態(tài)。Interface類型可以定義一組方法,但是這些不需要實(shí)現(xiàn)。并且interface不能包含任何變量。

    基本語法

    定義一個接口

    type Person interface {
        // 聲明方法
        method1(參數(shù)列表)返回值列表
        method2(參數(shù)列表)返回值列表
    }

    實(shí)現(xiàn)一個接口

    func (t 自定義類型)method1(參數(shù)列表)返回值列表 {
        //方法實(shí)現(xiàn)
    }
    func (t 自定義類型)method2(參數(shù)列表)返回值列表 {
        //方法實(shí)現(xiàn)
    }

    小結(jié):

    (1)接口里的所有方法都沒有方法體,即接口的方法都是沒有實(shí)現(xiàn)的方法。接口體現(xiàn)了程序設(shè)計的多態(tài)和高內(nèi)聚低耦合的思想。

    (2)Go中的接口,不需要顯示的實(shí)現(xiàn)。只要一個變量,含有接口類型中的所有方法,那么這個變量就實(shí)現(xiàn)這個接口。因此,Go中沒有implement關(guān)鍵字樣。

    (3)Go實(shí)現(xiàn)接口與方法有關(guān),與接口本身叫什么名字沒有特別大的關(guān)系。變量需要實(shí)現(xiàn)接口所有的方法。

    其他注意事項(xiàng)

    (1)接口本身不能創(chuàng)建實(shí)例,但是可以指向一個實(shí)現(xiàn)了該接口的自定義類型的變量(實(shí)例)。

    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)接口中所有的方法都沒有方法體,即都是沒有實(shí)現(xiàn)的方法。

    (3)在Go中,一個自定義類型需要將某個接口的所有方法都實(shí)現(xiàn),我們說這個自定義類型實(shí)現(xiàn)了該接口。

    (4)一個自定義類型只有實(shí)現(xiàn)了某個接口,才能將該自定義類型的實(shí)例(變量)賦給接口類型。

    (5)只要是自定義數(shù)據(jù)類型就可以實(shí)現(xiàn)接口,不僅僅是結(jié)構(gòu)體類型。

    (6)一個自定義類型可以實(shí)現(xiàn)多個接口。

    (7)Go接口不能有任何變量。

    (8)一個接口可以繼承多個別的接口,這時如果要實(shí)現(xiàn)這個接口必須實(shí)現(xiàn)它繼承的所有接口的方法。在低版本的Go編輯器中,一個接口繼承其他多個接口時,不允許繼承的接口有相同的方法名。比如A接口繼承B、C接口,B、C接口的方法名不能一樣。高版本的Go編輯器沒有相關(guān)問題。

    (9)interface類型默認(rèn)是一個指針(引用類型),如果沒有對interface初始化就使用,那么會輸出nil。

    (10)空接口interface{}沒有任何方法,所以所有類型都實(shí)現(xiàn)了空接口,即我們可以把任何一個變量賦給空接口類型。

    interface底層實(shí)現(xiàn)

    Go的interface源碼在Golang源碼的runtime目錄中。

    Go的interface是由兩種類型來實(shí)現(xiàn)的:iface和eface。

    iface

    iface是包含方法的interface,如:

    type Person interface {
    	Print()
    }

    iface的源代碼是:

    type iface struct {
    	tab  *itab
    	data unsafe.Pointer
    }

    iface具體結(jié)構(gòu)是:

    Go語言中interface語法與使用實(shí)例分析

    itab是iface不同于eface的關(guān)鍵數(shù)據(jù)結(jié)構(gòu)。其包含兩部分:一部分是唯一確定包含該interface的具體結(jié)構(gòu)類型,一部分是指向具體方法集的指針。其具體結(jié)構(gòu)為:

    Go語言中interface語法與使用實(shí)例分析

    屬性 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)為:

    Go語言中interface語法與使用實(shí)例分析

    我們來看一個例子,對于含有方法的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())
    }

    運(yùn)行結(jié)果:

    name:小明,age: 12

    Process finished with the exit code 0

    內(nèi)存分布示意圖:

    Go語言中interface語法與使用實(shí)例分析

    eface

    eface是不包含方法的interface,即空interface,如:

    type Person interface {
    }

    或者

    var person interface{} = xxxx實(shí)體

    侵入式與非侵入式的理解

    侵入式

    你的代碼里已經(jīng)嵌入了別的代碼,這些代碼可能是你引入過的框架,也可能是你通過接口繼承得來的,比如:java中的繼承,必須顯示的表明我要繼承那個接口,這樣你就可以擁有侵入代碼的一些功能。所以我們就稱這段代碼是侵入式代碼。

    優(yōu)點(diǎn):通過侵入代碼與你的代碼結(jié)合可以更好的利用侵入代碼提供給的功能。

    缺點(diǎn):框架外代碼就不能使用了,不利于代碼復(fù)用。依賴太多重構(gòu)代碼太痛苦了。

    非侵入式

    正好與侵入式相反,你的代碼沒有引入別的包或框架,完完全全是自主開發(fā)。比如go中的接口,不需要顯示的繼承接口,只需要實(shí)現(xiàn)接口的所有方法就叫實(shí)現(xiàn)了該接口,即便該接口刪掉了,也不會影響我,所有g(shù)o語言的接口數(shù)非侵入式接口;再如Python所崇尚的鴨子類型。

    優(yōu)點(diǎn):代碼可復(fù)用,方便移植。非侵入式也體現(xiàn)了代碼的設(shè)計原則:高內(nèi)聚,低耦合。

    缺點(diǎn):無法復(fù)用框架提供的代碼和功能。

    接下來看看java與go語言編程實(shí)現(xiàn)接口來理解侵入式與非侵入式的區(qū)別。

    java語言實(shí)現(xiàn)

    定義接口

    public interface IPersonService {
        String getName();
        Integer getAge();
    }

    實(shí)現(xiàn)接口的類

    public class PersonService implements IPersonService{
        @Override
        public String getName() {
            return "小明";
        }
        @Override
        public Integer getAge() {
            return 12;
        }
    }

    go語言實(shí)現(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é)了以下問題:

    1. 侵入式通過 implements 把實(shí)現(xiàn)類與具體接口綁定起來了,因此有了強(qiáng)耦合;

    2. 假如修改了接口方法,則實(shí)現(xiàn)類方法必須改動;

    3. 假如類想再實(shí)現(xiàn)一個接口,實(shí)現(xiàn)類也必須進(jìn)行改動;

    4. 后續(xù)實(shí)現(xiàn)此接口的類,必須了解相關(guān)的接口;
      Go語言非侵入式的方式很好地解決了這幾個問題,只要實(shí)現(xiàn)了實(shí)現(xiàn)了與接口相同的方法,就實(shí)現(xiàn)了這個接口。隨著代碼量的增加,根本不需要的關(guān)心實(shí)現(xiàn)了哪些接口,不需要刻意去先定義接口再實(shí)現(xiàn)接口的固定模式,在原有類新增實(shí)現(xiàn)接口時,不需要更改類,做到低侵入式、低耦合開發(fā)的好處。

    interface的應(yīng)用場景

    類型轉(zhuǎn)換

    類型推斷可將接口變量還原為原始類型,或用來判斷是否實(shí)現(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

    實(shí)現(xiàn)多態(tài)功能

    多態(tài)功能是interface實(shí)現(xiàn)的重要功能,也是Golang中的一大行為特色,其多態(tài)功能一般要結(jié)合Go method實(shí)現(xiàn),作為函數(shù)參數(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接受一個實(shí)現(xiàn)了notifier接口的值
    // 并發(fā)送通知
    func sendNotification(n notifier) {
      n.notify()
    }

    上述代碼中實(shí)現(xiàn)了一個多態(tài)的例子,函數(shù)sendNotification接受一個實(shí)現(xiàn)了notifier接口的值作為參數(shù)。既然任意一個實(shí)體類型都能實(shí)現(xiàn)該接口,那么這個函數(shù)可以針對任意實(shí)體類型的值來執(zhí)行notify方法,調(diào)用notify時,會根據(jù)對象的實(shí)際定義來實(shí)現(xiàn)不同的行為,從而實(shí)現(xiàn)多態(tài)行為。

    補(bǔ)充:interface 與 nil 的比較

    引用公司內(nèi)部同事的討論議題,覺得之前自己也沒有理解明白,為此,單獨(dú)羅列出來,例子是最好的說明,如下

    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傳進(jìn)去后,指向的類型的指針不為0了,因?yàn)橛蓄愋土耍?所以比較為false。 interface 類型比較, 要是 兩個指針都相等, 才能相等。

    以上就是關(guān)于“Go語言中interface語法與使用實(shí)例分析”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對大家有幫助,若想了解更多相關(guān)的知識內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道。

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI