interface
interface是go中一個非常重要的概念,使用的地方非常非常多瘾英,有必要好好學習。
- 那什么是接口呢做葵?
在現(xiàn)實中我們有usb接口护蝶、type-c接口华烟、HDMI接口,它們是一種約定,凡是實現(xiàn)了這個約定的電源線,插上就能有對應(yīng)的功能;
回到go中,go的接口也類似這樣,它是一種約定持灰,約定實現(xiàn)了xx方法盔夜,那么就實現(xiàn)了該接口。
- 接口有什么用呢堤魁?
一句話表述:可以實現(xiàn)多態(tài)和靈活的數(shù)據(jù)類型設(shè)計喂链、代碼接耦。
1. 接口定義
接口定義非常簡單
type 接口名稱 interface {
方法簽名
}
2. 多態(tài)實現(xiàn)
go是一個強類型語言, 有了接口妥泉,我們可以把具有相同功能的對象抽象為一個接口椭微,從而實現(xiàn)多態(tài)。
package main
import "fmt"
// 定義一個Speaker接口
type Speaker interface {
// 這個接口中需要包含一個say方法
say()
}
type Dog struct{}
// dog實現(xiàn)了Speaker接口
func (d Dog) say() {
fmt.Println("汪汪汪盲链!")
}
type Cat struct{}
// cat實現(xiàn)了Speaker接口
func (c Cat) say() {
fmt.Println("喵喵喵")
}
// 對Speaker切片元素調(diào)用say方法
func Speak(s []Speaker) {
for _, sp := range s {
sp.say() // 多態(tài)體現(xiàn)
}
}
func main() {
// 初始化一個長度為2的 Speaker接口類型的切片
s := make([]Speaker, 2)
s[0] = Cat{}
s[1] = Dog{}
Speak(s)
}
// 喵喵喵
// 汪汪汪蝇率!
3. 解耦合實現(xiàn)
假設(shè)在我們的項目中需要支持多種支付方式,比如微信支付刽沾、支付寶支付本慕、銀行卡支付,該如何實現(xiàn)呢悠轩?
package main
// 支付方式
type PaymentMethod interface {
// 支付
Pay(amount float32) error
// 退款
Refund(amount float32) error
}
// 支付寶支付
type AliPay struct{}
func (a *AliPay) Pay(amount float32) error {
// 支付操作
return nil
}
func (a *AliPay) Refund(amount float32) error {
// 支付操作
return nil
}
// 微信支付
type WeixinPay struct{}
func (a *WeixinPay) Pay(amount float32) error {
// 支付操作
return nil
}
func (a *WeixinPay) Refund(amount float32) error {
// 支付操作
return nil
}
// 操作支付
func DoPay(pm PaymentMethod, amount float32) {
// 實現(xiàn)于具體支付的接耦
pm.Pay(amount)
}
func main() {
pm := &AliPay{}
DoPay(pm, 100)
}
4. 值接收者 和 指針接收者
這是一個易錯點,需要特別注意哦攻泼!
在結(jié)構(gòu)體中火架,我們知道一個實例不區(qū)分值接收者還是指針接收者鉴象,都可以正常調(diào)用;但是在接口中不是這樣的何鸡,讓我們看下代碼:
package main
import "fmt"
// 薪水計算接口
type SalaryCalculator interface {
calSalary() int
}
// 永久員工
type Permant struct {
empId int
basicPay int // 基本薪資
performance int // 績效
}
// 實現(xiàn)薪水計算接口
func (p *Permant) calSalary() int {
return p.basicPay + p.performance
}
func main() {
p := Permant{empId: 1, basicPay: 100, performance: 50}
// 定義接口類型變量
var sc SalaryCalculator
sc = p // 發(fā)生錯誤
// cannot use p (variable of type Permant) as SalaryCalculator value in assignment: Permant does not implement SalaryCalculator (method calSalary has pointer receiver)
fmt.Println(sc.calSalary())
}
在上面的代碼中,由于接口中方法實現(xiàn)接收者是指針,所以必須保持接口對象也是指針纺弊,否則會報錯。
記住下面的規(guī)則就行:對于接口而言骡男,當方法是指針接收者時淆游,必須和接收者保持一致(指針),值接收者無所謂
為什么會這樣呢隔盛?
大概是編譯器在處理接口類型值轉(zhuǎn)指針時,轉(zhuǎn)換不了犹菱。
5. 空接口使用
空接口在go中的使用也是相當?shù)亩?主要用做任意類型值的做為參數(shù)傳入的場景。
interface {}
由于空接口中沒有任何方法簽名吮炕,所以任何類型都實現(xiàn)了空接口.這也是為什么空接口可以支持任意類型參數(shù)傳入的原因腊脱。
先來看一個示例
package main
import "fmt"
// 打印任何類型的東西
func printAny(v interface{}) {
fmt.Printf("%#v\n", v)
}
type Person struct {
name string
}
func main() {
// 任意類型隨便裝
printAny("abc")
printAny(12)
printAny(23.54)
printAny(Person{name: "sd"})
}
// "abc"
// 12
// 23.54
// main.Person{name:"sd"}
6. 類型斷言
在上面小結(jié)的代碼中,我們胡亂的往里面?zhèn)魅我忸愋偷闹?雖然代碼能運行,但是沒啥意義龙亲。
在真實的場景中陕凹,我們通常需要知道傳入的是什么數(shù)據(jù)類型,并對特定類型做相應(yīng)的處理鳄炉,這時候類型斷言就派上用場了杜耙。
類型斷言主要有兩種寫法:
-
x.(T)
// x是T類型,如果不是拂盯,panic -
val, ok := x.(T)
// 比較安全如果不是 ok為false
我們實踐下
package main
import "fmt"
func main() {
var a interface{}
a = "hello" // a當前是一個字符串類型
fmt.Println(a.(string)) // ok
// fmt.Println(a.(int)) // not ok 直接報錯
val, ok := a.(string)
fmt.Printf("val: %#v, ok: %v\n", val, ok)
}
// hello
// val: "hello", ok: true
如果類型很多,上面的ok模式就不太好用,go還提供了一種switch方式佑女,代碼如下:
package main
import "fmt"
func main() {
printAny("abc")
printAny(float32(1.23))
printAny(23)
printAny([]int{1, 2})
}
func printAny(v interface{}) {
// 注意這里寫的是type
switch val := v.(type) {
case string:
// 區(qū)分出類型后 再轉(zhuǎn)換成對應(yīng)類型
fmt.Printf("這是一個字符串: %s\n", val)
case float32:
fmt.Printf("這是一個32位浮點數(shù): %v\n", val)
case int:
fmt.Printf("這是一個整型:%v\n", val)
default:
fmt.Printf("Unknown type: %v\n", val)
}
}
// 這是一個字符串: abc
// 這是一個32位浮點數(shù): 1.23
// 這是一個整型:23
// Unknown type: [1 2]
6. 接口本質(zhì)
接口底層本質(zhì)是(type,value) 它們是一對的關(guān)系,任何一個接口都是由類型和值組成磕仅。
7. 空接口與nil值
接口相關(guān)的nil值和空指針結(jié)構(gòu)體比較非常容易讓人困惑珊豹,我們先來看一段代碼
package main
import "fmt"
type Person struct{}
func main() {
var a interface{} // 空接口
if a == nil {
fmt.Println("空接口等于nil的哦!")
}
var p *Person // 一個指向Person結(jié)構(gòu)體指針
if p == nil {
fmt.Println("結(jié)構(gòu)體空指針也等于nil")
}
//========= 上面很好理解榕订,關(guān)鍵看下面 ========//
var b interface{} // 定義一個空接口
b = p // 把結(jié)構(gòu)體空指針 賦給空接口
if b != nil {
fmt.Println("空結(jié)構(gòu)體指針接口不等于nil")
}
if b != a {
fmt.Println("空結(jié)構(gòu)體指針結(jié)構(gòu)體也不等于空接口")
}
}
// 空接口等于nil的哦店茶!
// 結(jié)構(gòu)體空指針也等于nil
// 空結(jié)構(gòu)體指針接口不等于nil
// 空結(jié)構(gòu)體指針結(jié)構(gòu)體也不等于空接口
怎么樣,對輸出有疑惑吧劫恒!我們大概總結(jié)下贩幻,
首先,無論對于空接口還是空結(jié)構(gòu)體指針它們都是 == nil
的
但是,空結(jié)構(gòu)體指針接口两嘴,既不等于nil
, 也不等于空接口
對這個問題困惑的原因丛楚,是因為對接口本質(zhì)不夠理解,接口的內(nèi)部本質(zhì)上是由(type,value)
一對組成的憔辫;
對于空接口趣些,它的類型和值都是nil;
對于空結(jié)構(gòu)體指針接口,它的類型是對應(yīng)結(jié)構(gòu)體指針類型贰您,值是nil;
所以很容易得出它們不相等坏平。
6. 接口嵌套(組合)
前面我們看到的都是單一接口拢操,接口是支持組合嵌套的,可以由多個單一接口組合成一個其它接口,比如:
// 定義基本接口
type Reader interface {
Read() string
}
// 在基本接口的基礎(chǔ)上定義擴展接口
type ReaderWithInfo interface {
Reader // 這里嵌套了另外一個接口
Info() string
}