天天看點

Go語言基礎之接口定義

Go語言基礎之接口定義

接口(interface)定義了一個對象的行為規範,隻定義規範不實作,由具體的對象來實作規範的細節。

一、接口類型

在Go語言中接口(interface)是一種類型,一種抽象的類型,引用類型。

interface

是一組

method

的集合,是

duck-type programming

的一種展現。接口做的事情就像是定義一個協定(規則),隻要一台機器有洗衣服和甩幹的功能,我就稱它為洗衣機。不關心屬性(資料),隻關心行為(方法)。

為了保護你的Go語言職業生涯,請牢記接口(interface)是一種類型。

二、 為什麼要使用接口

type Cat struct{}

func (c Cat) Say() string { return "喵喵喵" }

type Dog struct{}

func (d Dog) Say() string { return "汪汪汪" }

func main() {
    c := Cat{}
    fmt.Println("貓:", c.Say())
    d := Dog{}
    fmt.Println("狗:", d.Say())
}
           

上面的代碼中定義了貓和狗,然後它們都會叫,你會發現main函數中明顯有重複的代碼,如果我們後續再加上豬、青蛙等動物的話,我們的代碼還會一直重複下去。那我們能不能把它們當成“能叫的動物”來處理呢?

像類似的例子在我們程式設計過程中會經常遇到:

比如一個網上商城可能使用支付寶、微信、銀聯等方式去線上支付,我們能不能把它們當成“支付方式”來處理呢?

比如三角形,四邊形,圓形都能計算周長和面積,我們能不能把它們當成“圖形”來處理呢?

比如銷售、行政、程式員都能計算月薪,我們能不能把他們當成“員工”來處理呢?

Go語言中為了解決類似上面的問題,就設計了接口這個概念。接口差別于我們之前所有的具體類型,接口是一種抽象的類型。當你看到一個接口類型的值時,你不知道它是什麼,唯一知道的是通過它的方法能做什麼。

三、接口的定義

Go語言提倡面向接口程式設計。

每個接口由數個方法組成,接口的定義格式如下:

type 接口類型名 interface{
    方法名1( 參數清單1 ) 傳回值清單1
    方法名2( 參數清單2 ) 傳回值清單2
    …
}
           

其中:

  • 接口名:使用

    type

    将接口定義為自定義的類型名。Go語言的接口在命名時,一般會在單詞後面添加

    er

    ,如有寫操作的接口叫

    Writer

    ,有字元串功能的接口叫

    Stringer

    等。接口名最好要能突出該接口的類型含義。
  • 方法名:當方法名首字母是大寫且這個接口類型名首字母也是大寫時,這個方法可以被接口所在的包(package)之外的代碼通路。
  • 參數清單、傳回值清單:參數清單和傳回值清單中的參數變量名可以省略。

舉個例子:

type writer interface{
    Write([]byte) error
}
           

當你看到這個接口類型的值時,你不知道它是什麼,唯一知道的就是可以通過它的Write方法來做一些事情。

四、實作接口的條件

一個對象隻要全部實作了接口中的方法,那麼就實作了這個接口。換句話說,接口就是一個需要實作的方法清單。

我們來定義一個

Sayer

接口:

// Sayer 接口
type Sayer interface {
    say()
}
           

定義

dog

cat

兩個結構體:

type dog struct {}

type cat struct {}
           

因為

Sayer

接口裡隻有一個

say

方法,是以我們隻需要給

dog

cat

分别實作

say

方法就可以實作

Sayer

接口了。

// dog實作了Sayer接口
func (d dog) say() {
    fmt.Println("汪汪汪")
}

// cat實作了Sayer接口
func (c cat) say() {
    fmt.Println("喵喵喵")
}
           

接口的實作就是這麼簡單,隻要實作了接口中的所有方法,就實作了這個接口。

五、接口類型變量

那實作了接口有什麼用呢?

接口類型變量能夠存儲所有實作了該接口的執行個體。 例如上面的示例中,

Sayer

類型的變量能夠存儲

dog

cat

類型的變量。

func main() {
    var x Sayer // 聲明一個Sayer類型的變量x
    a := cat{}  // 執行個體化一個cat
    b := dog{}  // 執行個體化一個dog
    x = a       // 可以把cat執行個體直接指派給x
    x.say()     // 喵喵喵
    x = b       // 可以把dog執行個體直接指派給x
    x.say()     // 汪汪汪
}
           

如果我們僅僅隻是想驗證一下,某個結構體是否實作了接口,可以這麼做

// 1、如果下述代碼可以指派成功,則證明結構體實作了接口,因為此處我們隻想驗證,是以變量名無關緊要,推薦用_代表
var _ 接口類型 = &結構體{}  

// 2、例如:
var _ DuckInterface = &PDuck{}
           
Go語言基礎之接口定義
// 摘自gin架構routergroup.go 體味此處_的妙用
type IRouter interface{ ... }

type RouterGroup struct { ... }

var _ IRouter = &RouterGroup{}  // 確定RouterGroup實作了接口IRouter
           

任意的類型都可以指派給空接口類型

type Empty interface {
}
//3 空接口(沒有方法,所有類型其實都實作了空接口,于是:任意的類型都可以指派給空接口類型)
var a Empty=10
a="ssddd"
a = TDuck{}
fmt.Println(a)
           

六、接口零值

接口的零值是

nil

。對于值為

nil

的接口,其底層值(Underlying Value)和具體類型(Concrete Type)都為

nil

package main

import "fmt"

type Describer interface {  
    Describe()
}

func main() {  
    var d1 Describer
    if d1 == nil {
        fmt.Printf("d1 is nil and has type %T value %v\n", d1, d1)
    }
}
           

上面程式裡的

d1

等于

nil

,程式會輸出:

d1 is nil and has type <nil> value <nil>
           

對于值為

nil

的接口,由于沒有底層值和具體類型,當我們試圖調用它的方法時,程式會産生

panic

異常。

package main

type Describer interface {
    Describe()
}

func main() {  
    var d1 Describer
    d1.Describe()
}
           

在上述程式中,

d1

等于

nil

,程式産生運作時錯誤

panic

: panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0xffffffff addr=0x0 pc=0xc8527] 。

七、總結

  1. 接口定義
    • 接口名單詞後面加

      er

      ,表示是接口
    • 接口名和方法名大寫表示可以被包之外代碼可以通路
    type 接口類型名 interface{
        方法名1( 參數清單1 ) 傳回值清單1
        方法名2( 參數清單2 ) 傳回值清單2
        …
    }
               
  2. 如果一個結構體實作接口中所有的方法,那麼就實作了這個接口

在當下的階段,必将由程式員來主導,甚至比以往更甚。