Go 学习笔记(71)— Go 接口 interface (接口定义、接口实现、接口调用、值接收者、指针接收者)
1. 接口的定義
接口是和調用方的一種約定,它是一個高度抽象的類型,不用和具體的實現細節綁定在一起。接口要做的是定義好約定,告訴調用方自己可以做什么,但不用知道它的內部實現,這和我們見到的具體的類型如 int、map、slice 等不一樣。
接口的定義和結構體稍微有些差別,雖然都以 type 關鍵字開始,但接口的關鍵字是 interface,表示自定義的類型是一個接口。
也就是說 person 是一個接口,它有兩個方法 sayName() string 和 sayAge() int,整體如下面的代碼所示:
type person interface {sayName() stringsayAge() int
}
}
針對 person 接口來說,它會告訴調用者可以通過它的 sayName() 方法獲取姓名字符串,通過它的 sayAge() 方法獲取年齡,這就是接口的約定。至于這個字符串怎么獲得的,長什么樣,接口不關心,調用者也不用關心,因為這些是由接口實現者來做的。
接口特點:
- 接口只有方法聲明、沒有實現,沒有數據字段
- 接口可以匿名嵌入其它接口,或者嵌入到結構中
接口是用來定義行為的類型,這些被定義的行為不由接口直接實現,而是由用戶定義的類型實現,一個實現了這些方法的具體類型是這個接口類型的實例。
如果用戶定義的類型實現了某個接口類型聲明的一組方法,那么這個用戶定義的類型的值就可以賦給這個接口類型的值。這個賦值會把用戶定義的類型存入接口類型的值。
2. 接口的實現
接口的實現者必須是一個具體的類型,以 student 結構體為例,
type student struct {name stringage int
}
讓它來實現 person 接口,如下代碼所示:
func (s student) sayName() string {fmt.Printf("name is %v\n", s.name)return s.name
}func (s student) sayAge() int {fmt.Printf("name is %v\n", s.age)return s.age
}
給結構體類型 student 定義一個方法,這個方法和接口里方法的簽名(名稱、參數和返回值)一樣,這樣結構體 student 就實現了 person接口。
注意:如果一個接口有多個方法,那么需要實現接口的每個方法才算是實現了這個接口。
3. 接口賦值
接口賦值在 Go 語言中分為如下兩種情況:
- 將對象實例賦值給接口;
- 將一個接口賦值給另一個接口。
3.1 對象實例賦值給接口
先討論將某種類型的對象實例賦值給接口,這要求該對象實例實現了接口要求的所有方法,如下:
type Integer intfunc (a Integer) Less(b Integer) bool {return a < b
}func (a *Integer) Add(b Integer) {*a += b
}
相應地,我們定義接口 LessAdder ,如下:
type LessAdder interface {Less(b Integer) boolAdd(b Integer)
}
現在有個問題:假設我們定義一個 Integer 類型的對象實例,怎么將其賦值給 LessAdder接口呢?
應該用下面的語句(1),還是語句(2)呢?
var a Integer = 1
var b LessAdder = &a ... (1)
var b LessAdder = a ... (2)
答案是應該用語句(1)。原因在于,Go 語言可以根據下面的函數:
func (a Integer) Less(b Integer) bool {return a < b
}
自動生成一個新的 Less() 方法:
func (a *Integer) Less(b Integer) bool {return (*a).Less(b)
}
這樣,類型 *Integer 就既存在 Less() 方法,也存在 Add() 方法,滿足 LessAdder 接口。
而從另一方面來說,根據
func (a *Integer) Add(b Integer)
這個函數無法自動生成以下這個成員方法:
func (a Integer) Add(b Integer) {(&a).Add(b)
}
因為 (&a).Add() 改變的只是函數參數a,對外部實際要操作的對象并無影響,這不符合用
戶的預期。
所以,Go 語言不會自動為其生成該函數。因此,類型 Integer 只存在 Less()方法,缺少 Add() 方法,不滿足 LessAdder 接口,故此上面的語句(2)不能賦值。
為了進一步證明以上的推理,我們不妨再定義一個 Lesser 接口,如下:
type Lesser interface {Less(b Integer) bool
}
然后定義一個 Integer 類型的對象實例,將其賦值給 Lesser 接口:
var a Integer = 1
var b1 Lesser = &a ... (1)
var b2 Lesser = a ... (2)
正如我們所料的那樣,語句(1)和語句(2)均可以編譯通過。
3.2 將一個接口賦值給另一個接口
在 Go 語言中,只要兩個接口擁有相同的方法列表(次序不同不要緊),那么它們就是等同的,可以相互賦值。
4. 接口調用
實現了 person 接口后就可以使用了。首先我先來定義一個可以打印 person 接口的函數,如下所示:
func printPersonInfo(p person) {fmt.Printf("name is %v\n", p.sayName())fmt.Printf("age is %v\n", p.sayAge())
}
這個被定義的函數 printPersonInfo,它接收一個 person 接口類型的參數,然后打印出 person 接口的 sayName() 方法返回的字符串,sayAge() 方法返回年齡
printPersonInfo 這個函數的優勢就在于它是面向接口編程的,只要一個類型實現了 Stringer 接口,都可以打印出對應的字符串,而不用管具體的類型實現。
因為 student 實現了 person 接口,所以變量 stu 可以作為函數 printPersonInfo 的參數,可以用如下方式打印:
printPersonInfo(stu)
輸出
name is wohu
age is 20
現在讓結構體 teacher 也實現 person 接口,如下面的代碼所示:
type teacher struct {name stringage int
}func (t teacher) sayName() string {return t.name
}func (t teacher) sayAge() int {return t.age
}
因為結構體 teacher 也實現了 person 接口,所以 printPersonInfo 函數不用做任何改變,可以直接被使用,如下所示:
printPersonInfo(t)
輸出結果:
name is Jack
age is 40
這就是面向接口的好處,只要定義和調用雙方滿足約定,就可以使用,而不用管具體實現。接口的實現者也可以更好的升級重構,而不會有任何影響,因為接口約定沒有變。
5. 值接收者和指針接收者
我們已經知道,如果要實現一個接口,必須實現這個接口提供的所有方法,而且在上節課講解方法的時候,定義一個方法,有值類型接收者和指針類型接收者兩種。二者都可以調用方法,因為 Go 語言編譯器自動做了轉換,所以值類型接收者和指針類型接收者是等價的。但是在接口的實現中,值類型接收者和指針類型接收者不一樣,下面我會詳細分析二者的區別。
上面已經驗證了結構體類型實現了 person 接口,那么結構體對應的指針是否也實現了該接口呢?我通過下面這個代碼進行測試:
printPersonInfo(&s)
輸出結果:
name is wohu
age is 20
測試后會發現,把變量 t 的指針作為實參傳給 printPersonInfo 函數也是可以的,編譯運行都正常。這就證明了以值類型接收者實現接口的時候,不管是類型本身,還是該類型的指針類型,都實現了該接口。
示例中值接收者(s student)實現了 person 接口,那么類型 student 和它的指針類型 *student 就都實現了 person 接口。
現在,我把接收者改成指針類型,如下代碼所示:
func (s *student) sayName() string {return s.name
}func (s *student) sayAge() int {return s.age
}
修改成指針類型接收者后會發現,示例中這行 printPersonInfo(stu) 代碼編譯不通過,提示如下錯誤:
demo.go:46:17: cannot use stu (type student) as type person in argument to printPersonInfo:student does not implement person (sayAge method has pointer receiver)
意思就是類型 student 沒有實現 person 接口。這就證明了以指針類型接收者實現接口的時候,只有對應的指針類型才被認為實現了該接口。
總結:
-
當值類型作為接收者時,
student類型和*student類型都實現了該接口; -
當指針類型作為接收者時,只有
*student類型實現了該接口;
可以發現,實現接口的類型都有 *student,這也表明指針類型比較萬能,不管哪一種接收者,它都能實現該接口。
package mainimport "fmt"type student struct {name stringage int
}type teacher struct {name stringage int
}type person interface {sayName() stringsayAge() int
}func (t teacher) sayName() string {return t.name
}func (t teacher) sayAge() int {return t.age
}func (s *student) sayName() string {// fmt.Printf("name is %v\n", s.name)return s.name
}func (s *student) sayAge() int {// fmt.Printf("age is %v\n", s.age)return s.age
}func printPersonInfo(p person) {fmt.Printf("name is %v\n", p.sayName())fmt.Printf("age is %v\n", p.sayAge())
}func main() {stu := student{name: "wohu", age: 20}// t := teacher{name: "Jack", age: 40}printPersonInfo(stu)// printPersonInfo(t)}
總結
以上是生活随笔為你收集整理的Go 学习笔记(71)— Go 接口 interface (接口定义、接口实现、接口调用、值接收者、指针接收者)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 2022-2028年中国聚硫橡胶行业市场
- 下一篇: 2022-2028年中国丙烯酸酯橡胶行业