Go 專欄|介面 interface
原文連結: Go 專欄|介面 interface
Duck Typing,鴨子型別,在維基百科裡是這樣定義的:
If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
翻譯過來就是:如果某個東西長得像鴨子,游泳像鴨子,嘎嘎叫像鴨子,那它就可以被看成是一隻鴨子。
它是動態程式語言的一種物件推斷策略,它更關注物件能做什麼,而不是物件的型別本身。
例如:在動態語言 Python 中,定義一個這樣的函式:
def hello_world(duck):
duck.say_hello()
當呼叫此函式的時候,可以傳入任意型別,只要它實現了 say_hello()
就可以。如果沒實現,執行過程中會出現錯誤。
Go 語言作為一門靜態語言,它通過介面的方式完美支援鴨子型別。
介面型別
之前介紹的型別都是具體型別,而介面是一種抽象型別,是多個方法宣告的集合。在 Go 中,只要目標型別實現了介面要求的所有方法,我們就說它實現了這個介面。
先來看一個例子:
package main
import "fmt"
// 定義介面,包含 Eat 方法
type Duck interface {
Eat()
}
// 定義 Cat 結構體,並實現 Eat 方法
type Cat struct{}
func (c *Cat) Eat() {
fmt.Println("cat eat")
}
// 定義 Dog 結構體,並實現 Eat 方法
type Dog struct{}
func (d *Dog) Eat() {
fmt.Println("dog eat")
}
func main() {
var c Duck = &Cat{}
c.Eat()
var d Duck = &Dog{}
d.Eat()
s := []Duck{
&Cat{},
&Dog{},
}
for _, n := range s {
n.Eat()
}
}
使用 type
關鍵詞定義介面:
type Duck interface {
Eat()
}
介面包含了一個 Eat()
方法,然後定義兩個結構體型別 Cat
和 Dog
,分別實現了 Eat
方法。
// 定義 Cat 結構體,並實現 Eat 方法
type Cat struct{}
func (c *Cat) Eat() {
fmt.Println("cat eat")
}
// 定義 Dog 結構體,並實現 Eat 方法
type Dog struct{}
func (d *Dog) Eat() {
fmt.Println("dog eat")
}
遍歷介面切片,通過介面型別可以直接呼叫對應方法:
s := []Duck{
&Cat{},
&Dog{},
}
for _, n := range s {
n.Eat()
}
// 輸出
// cat eat
// dog eat
介面賦值
介面賦值分兩種情況:
- 將物件例項賦值給介面
- 將一個介面賦值給另一個介面
下面來分別說說:
將物件例項賦值給介面
還是用上面的例子,因為 Cat
實現了 Eat
介面,所以可以直接將 Cat
例項賦值給介面。
var c Duck = &Cat{}
c.Eat()
在這裡一定要傳結構體指標,如果直接傳結構體會報錯:
var c Duck = Cat{}
c.Eat()
# command-line-arguments
./09_interface.go:25:6: cannot use Cat{} (type Cat) as type Duck in assignment:
Cat does not implement Duck (Eat method has pointer receiver)
但是如果反過來呢?比如使用結構體來實現介面,使用結構體指標來賦值:
// 定義 Cat 結構體,並實現 Eat 方法
type Cat struct{}
func (c Cat) Eat() {
fmt.Println("cat eat")
}
var c Duck = &Cat{}
c.Eat() // cat eat
沒有問題,可以正常執行。
將一個介面賦值給另一個介面
還是上面的例子,可以直接將 c
的值直接賦值給 d
:
var c Duck = &Cat{}
c.Eat()
var d Duck = c
d.Eat()
再來,我再定義一個介面 Duck1
,這個介面包含兩個方法 Eat
和 Walk
,然後結構體 Dog
實現兩個方法,但是 Cat
只實現 Eat
方法。
type Duck1 interface {
Eat()
Walk()
}
// 定義 Dog 結構體,並實現 Eat 方法
type Dog struct{}
func (d *Dog) Eat() {
fmt.Println("dog eat")
}
func (d *Dog) Walk() {
fmt.Println("dog walk")
}
那麼在賦值時,使用 Duck1
賦值給 Duck
是可以的,反過來就會報錯。
var c1 Duck1 = &Dog{}
var c2 Duck = c1
c2.Eat()
所以,已經初始化的介面變數 c1
直接賦值給另一個介面變數 c2
,要求 c2
的方法集是 c1
的方法集的子集。
空介面
具有 0 個方法的介面稱為空介面,它表示為 interface {}
。由於空介面有 0 個方法,所以所有型別都實現了空介面。
func main() {
// interface 形參
s1 := "Hello World"
i := 50
strt := struct {
name string
}{
name: "AlwaysBeta",
}
test(s1)
test(i)
test(strt)
}
func test(i interface{}) {
fmt.Printf("Type = %T, value = %v\n", i, i)
}
型別斷言
型別斷言是作用在介面值上的操作,語法如下:
x.(T)
其中 x
是介面型別的表示式,T
是斷言型別。
作用是判斷運算元的動態型別是否滿足指定的斷言型別。
有兩種情況:
T
是具體型別T
是介面型別
下面來分別舉例說明:
具體型別
型別斷言會檢查 x
的動態型別是否為 T
,如果是,則輸出 x
的值;如果不是,程式直接 panic
。
func main() {
// 型別斷言
var n interface{} = 55
assert(n) // 55
var n1 interface{} = "hello"
assert(n1) // panic: interface conversion: interface {} is string, not int
}
func assert(i interface{}) {
s := i.(int)
fmt.Println(s)
}
介面型別
型別斷言會檢查 x
的動態型別是否滿足介面型別 T
,如果滿足,則輸出 x
的值,這個值可能是繫結例項的副本,也可能是指標的副本;如果不滿足,程式直接 panic
。
func main() {
// 型別斷言
assertInterface(c) // &{}
}
func assertInterface(i interface{}) {
s := i.(Duck)
fmt.Println(s)
}
如果有兩個接收值,那麼斷言不會在失敗時崩潰,而是會多返回一個布林值,一般命名為 ok
,來表示斷言是否成功。
func main() {
// 型別斷言
var n1 interface{} = "hello"
assertFlag(n1)
}
func assertFlag(i interface{}) {
if s, ok := i.(int); ok {
fmt.Println(s)
}
}
型別查詢
語法類似型別斷言,只需將 T
直接用關鍵詞 type
替代。
作用主要有兩個:
- 查詢一個介面變數繫結的底層變數型別
- 查詢一個介面變數的底層變數是否還實現了其他介面
func main() {
// 型別查詢
SearchType(50) // Int: 50
SearchType("zhangsan") // String: zhangsan
SearchType(c) // dog eat
SearchType(50.1) // Unknown type
}
func SearchType(i interface{}) {
switch v := i.(type) {
case string:
fmt.Printf("String: %s\n", i.(string))
case int:
fmt.Printf("Int: %d\n", i.(int))
case Duck:
v.Eat()
default:
fmt.Printf("Unknown type\n")
}
}
總結
本文從鴨子型別引出 Go 的介面,然後用一個例子簡單展示了介面型別的用法,接著又介紹了介面賦值,空介面,型別斷言和型別查詢。
相信通過本篇文章大家能對介面有了整體的概念,並掌握了基本用法。
文章中的腦圖和原始碼都上傳到了 GitHub,有需要的同學可自行下載。
地址: http://github.com/yongxinz/gopher/tree/main/sc
Go 專欄文章列表:
- Mysql 資料庫查詢好慢,除了索引,還能因為什麼?
- 10 個寶藏級程式設計資源,我激動到下跪!
- Python 學習路線(2022)
- Python 學習路線(2022)
- 程式設計師全職接單一個月的感觸
- 一個關於 = 的謎題
- 6 輪面試,進微軟了
- 這次,讓我們捋清:同步、非同步、阻塞、非阻塞
- 如何在 Go 中將 []byte 轉換為 io.Reader?
- 如何在 GitHub 上高效閱讀原始碼?
- 圖解|一個跨域問題給我整懵了
- 一勞永逸,使用 PicGo GitHub 搭建個人圖床工具
- 京東面試題:Elasticsearch 深度分頁解決方案
- 1 分鐘學會 30 種程式語言
- 聽說,99% 的 Go 程式設計師都被 defer 坑過
- gRPC,爆贊
- 推薦三個實用的 Go 開發工具
- Go 專欄|介面 interface
- Go 專欄|基礎資料型別:整數、浮點數、複數、布林值和字串
- 獲取 Django 專案下全部 URL,一個函式輕鬆搞定