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
…
}
其中:
- 接口名:使用
将接口定義為自定義的類型名。Go語言的接口在命名時,一般會在單詞後面添加type
,如有寫操作的接口叫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{}

// 摘自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] 。
七、總結
- 接口定義
- 接口名單詞後面加
,表示是接口er
- 接口名和方法名大寫表示可以被包之外代碼可以通路
type 接口類型名 interface{ 方法名1( 參數清單1 ) 傳回值清單1 方法名2( 參數清單2 ) 傳回值清單2 … }
- 接口名單詞後面加
- 如果一個結構體實作接口中所有的方法,那麼就實作了這個接口
在當下的階段,必将由程式員來主導,甚至比以往更甚。