Go介面詳談
在Go中使用interface關鍵字宣告一個介面:
type Shaper interface { Area() float64 Perimeter() float64 }
如果我們直接使用fmt庫進行輸出,會得到什麼結果呢?
func main() { var s Shaper fmt.Println("value of s is ", s) fmt.Printf("type of s is %T\n", s) }
輸出:
value of s istype of s is
在這裡,引出介面的概念。介面有兩種型別。介面的靜態型別是介面本身,例如上述程式中的Shape。介面沒有靜態值,而是指向動態值。
介面型別的變數可以儲存實現介面的型別的值。該型別的值成為介面的動態值,並且該型別成為介面的動態型別。
從上面的示例開始,我們可以看到零值和介面的型別為nil。這是因為,此刻,我們已宣告型別Shaper的變數s,但未分配任何值。當我們使用帶有介面引數的fmt包中的Println函式時,它指向介面的動態值,Printf功能中的%T語法是指動態型別的介面。實際上,介面靜態型別是Shaper。
當我們使用一個型別去實現該介面後,會是什麼效果。
type Rect struct { width float64 height float64 } func (r Rect) Area() float64 { return r.width * r.height } func (r Rect) Perimeter() float64 { return 2 * (r.width + r.height) } // main func main() { var s Shaper fmt.Println("value of s is ", s) fmt.Printf("type of s is %T\n", s) s = Rect{5.0, 4.0} r := Rect{5.0, 4.0} fmt.Printf("type of s is %T\n", s) fmt.Printf("value of s is %v\n", s) fmt.Printf("area of rect is %v\n", s.Area()) fmt.Println("s == r is", s == r) }
輸出:
value of s istype of s istype of s is main.Rect value of s is {5 4} area of rect is 20 s == r is tru
可以看到此時s變成了動態型別,儲存的是main.Rect,值變成了{5,4}。
有時,動態型別的介面也稱為具體型別,因為當我們訪問介面型別時,它會返回其底層動態值的型別,並且其靜態型別保持隱藏。
我們可以在s上呼叫Area方法,因為介面Shaper定義了Area方法,而s的具體型別是Rect,它實現了Area方法。該方法將在介面儲存的動態值上被呼叫。
此外,我們可以看到我們可以使用s與r進行比較,因為這兩個變數都儲存相同的動態型別(Rect型別的結構)和動態值{5 4}。
我們接著使用圓來實現該介面:
type Circle struct { radius float64 } func (c Circle) Area() float64 { return 3.14 * c.radius * c.radius } func (c Circle) Perimeter() float64 { return 2 * 3.14 * c.radius } // main s = Circle{10} fmt.Printf("type of s is %T\n", s) fmt.Printf("value of s is %v\n", s) fmt.Printf("area of rect is %v\n", s.Area())
此時輸出:
type of s is main.Circle value of s is {10} area of rect is 314
這裡進一步理解了介面儲存的動態型別。從切片角度出發,可以說,介面也以類似的方式工作,即動態儲存對底層型別的引用。
當我們刪除掉Perimeter的實現,可以看到如下報錯結果。
./rect.go:34:4: cannot use Rect{...} (type Rect) as type Shaper in assignment: Rect does not implement Shaper (missing Perimeter method)
從上面的錯誤應該是顯而易見的,為了成功實現介面,需要實現與完全簽名的介面宣告的所有方法。
當一個介面沒有任何方法時,它被稱為空介面。這由介面{}表示。因為空介面沒有方法,所以所有型別都隱式地實現了這個介面。
空介面的作用之一在於:函式可以接收多個不同型別引數。
例如:fmt的Println函式。
func Println(a ...interface{}) (n int, err error) Println是一個可變函式,它接受interface{}型別的引數。
例如:
type MyString string func explain(i interface{}) { fmt.Printf("type: %T, value: %v\n", i, i) } // main s := MyString("hello") explain(s) r := Rect{1, 2} explain(r)
輸出:
type: inter.MyString, value: hello type: inter.Rect, value: {1 2}
可以看到空介面的型別與值是動態的。
在下面的程式中,我們用Area方法建立了Shape介面,用Volume方法建立了Object介面。因為結構型別Cube實現了這兩個方法,所以它實現了這兩個介面。因此,我們可以將結構型別Cube的值賦給型別為Shape或Object的變數。
type IShape interface { Area() float64 } type Object interface { Volume() float64 } type Cube struct { side float64 } func (c Cube) Area() float64 { return 6 * c.side * c.side } func (c Cube) Volume() float64 { return c.side * c.side * c.side } // main c := Cube{3} var s IShape = c var o Object = c fmt.Println("area is", s.Area()) fmt.Println("Volume is", o.Volume())
這種呼叫是沒有問題的,呼叫各自動態型別的方法。
那如果是這樣呢?
fmt.Println("area of s of interface type IShape is", s.Volume()) fmt.Println("volume of o of interface type Object is", o.Area())
輸出:
s.Volume undefined (type Shape has no field or method Volume) o.Area undefined (type Object has no field or method Area)
這個程式無法編譯,因為s的靜態型別是IShape,而o的靜態型別是Object。因為IShape沒有定義Volume方法,Object也沒有定義Area方法,所以我們得到了上面的錯誤。
要使其工作,我們需要以某種方式提取這些介面的動態值,這是一個立方體型別的結構體,立方體實現了這些方法。這可以使用型別斷言來完成。
我們可以透過i.(Type)確定介面i的底層動態值,Go將檢查i的動態型別是否與type相同,並返回可能的動態值。
var s1 IShape = Cube{3} c1 := s1.(Cube) fmt.Println("area of s of interface type IShape is", c1.Volume()) fmt.Println("volume of o of interface type Object is", c1.Area())
這樣便可以正常工作了。
如果IShape沒有儲存Cube型別,且Cube沒有實現IShape,那麼報錯:
impossible type assertion: Cube does not implement IShape (missing Area method)
如果IShape沒有儲存Cube型別,且Cube實現Shape,那麼報錯:
panic: interface conversion: inter.IShape is nil, not inter.Cub
幸運的是,語法中還有另一個返回值:
value, ok := i.(Type)
在上面的語法中,如果i有具體的type型別或type的動態值,我們可以使用ok變數來檢查。如果不是,那麼ok將為假,value將為Type的零值(nil)。
此外,使用型別斷言可以檢查該介面的動態型別是否實現了其他介面,就像前面的IShape的動態型別是Cube,它實現了IShape、Object介面,如下例子:
vaule1, ok1 := s1.(Object) value2, ok2 := s1.(Skin) fmt.Printf("IShape s的動態型別值是: %v, 該動態型別是否實現了Object介面: %v\n", vaule1, ok1) fmt.Printf("IShape s的動態型別值是: %v, 該動態型別是否實現了Skin介面: %v\n", value2, ok2)
輸出:
IShape s的動態型別值是: {3}, 該動態型別是否實現了Object介面: true IShape s的動態型別值是:, 該動態型別是否實現了Skin介面: false
型別斷言不僅用於檢查介面是否具有某個給定型別的具體值,而且還用於將介面型別的給定變數轉換為不同的介面型別。
在前面的空介面中,我們知道將一個空介面作為函式引數,那麼該函式可以接受任意型別,那如果我有一個需求是:當傳遞的資料型別是字串時,要求全部變為大寫,其他型別不進行操作?
針對這樣的需求,我們可以採用Type Switch,即:i.(type)+switch。
func switchProcess(i interface{}) { switch i.(type) { case string: fmt.Println("process string") case int: fmt.Println("process int") default: fmt.Printf("type is %T\n", i) } }
輸出:
process int process string
在Go中,一個介面不能實現或擴充套件其他介面,但我們可以透過合併兩個或多個介面來建立一個新的介面。
例如:
這裡使用Runner與Eater兩個介面,組合成了一個新介面RunEater,該介面為Embedding interfaces。
type Runner interface { run() string } type Eater interface { eat() string } type RunEater interface { Runner Eater } type Dog struct { age int } func (d Dog) run() string { return "run" } func (d Dog) eat() string { return "eat" } // main d := Dog{10} var re RunEater = d var r Runner = d var e Eater = d fmt.Printf("RunnEater dynamic type: %T, value: %v\n", re, re) fmt.Printf("Runn dynamic type: %T, value: %v\n", r, r) fmt.Printf("Eater dynamic type: %T, value: %v\n", e, e)
輸出:
RunnEater dynamic type: inter.Dog, value: {10} Runn dynamic type: inter.Dog, value: {10} Eater dynamic type: inter.Dog, value: {10}
如果基礎動態值為nil,則兩個介面總是相等的,這意味著兩個nil介面總是相等的,因此== operation返回true。
var a, b interface{} fmt.Println( a == b ) // true
如果這些介面不是nil,那麼它們的動態型別(具體值的型別)應該相同,具體值應該相等。
如果介面的動態型別不具有可比性,例如slice、map、function,或者介面的具體值是包含這些不可比較性值的複雜資料結構,如切片或陣列,則==或!=操作將導致執行時panic。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2782455/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Go 語言介面詳解(一)Go
- Go 語言介面詳解(二)Go
- go介面Go
- Go語言之介面Go
- Go 介面型別Go型別
- 談談Spring中的BeanPostProcessor介面SpringBean
- go語言的介面Go
- 曹春暉:談一談 Go 和 SyscallGo
- 談談Spring中的BeanPostProcessor介面(轉)SpringBean
- Go team 訪談上線Go
- Go Errors 詳解GoError
- Go 介面:nil介面為什麼不等於nil?Go
- go sort.Interface 排序介面Go排序
- go語言學習-介面Go
- Go 介面 學習筆記Go筆記
- Go 語言 nil 和介面Go
- Go 介面:Go中最強大的魔法,介面應用模式或慣例介紹Go模式
- 淺談JavaScript中的介面JavaScript
- 淺談web介面測試Web
- Git workflow 詳談Git
- [Go語言寫介面]一、使用xcgui完成go語言第一個軟體介面GoGUI
- Go標準庫:Go template用法詳解Go
- interface 介面 -Go 學習記錄Go
- 【Go進階—基礎特性】介面Go
- [譯] 如何在 Go 中使用介面Go
- Go 介面所在原始碼包定位Go原始碼
- 使用 Ruby on Rails 開發 Go 介面AIGo
- 詳解 go 的切片Go
- Go Modules 詳解使用Go
- Go 通道(chanel)詳解Go
- 談談依賴注入與面向介面程式設計依賴注入程式設計
- 清華尹成帶你實戰GO案例(38)Go 介面Go
- Java基礎之淺談介面Java
- 詳談webpack4Web
- Bitmap優化詳談優化
- 介面測試--apipost介面斷言詳解API
- 淘寶詳情api介面API
- 商品詳情API介面API