【14】GO语言的接口类型
1、什么是接口?
在面向對象的語言中,接口是用來限制實現類行為的。怎么理解這句話呢?
定義一個Person接口,我只會站在我的角度上考慮問題,比如Person(人),自然想到會吃飯、睡覺等:
| interface Person { ? ? ??// 人會吃飯 ? ? ? void eat(); ? ? ??// 人會睡覺 ? ? ? void sleep(); } |
我是站在接口角度上考慮接口如何定義,此時不會過多考慮實現類的行為。
這很正常,因為我不能確定誰會使用我的接口,有一天SuperMan說:“我要用你定義的接口”,那SuperMan必須用implements實現Person接口的行為:
| // SuperMan實現Person接口 public class SuperMan?implements?Person { ? ? ??// 超人會吃飯 ? ? ? ?public void eat() ? ? ? ?{ ? ? ? ? ? ? ? System.out.println("super man can eat."); ? ? ? ?} ? ? ? ?// 超人會睡覺 ? ? ? ?public void sleep() ? ? ? ?{ ? ? ? ? ? ? ? System.out.println("super man can sleep."); ? ? ? ?} } |
等到SuperMan實現完了之后,他對我說:“作為超人,我是會飛的哦~”
這時作為Person定義者的我,只有兩個選擇:
-
對SuperMan說:“飛是你自己的行為,我不幫你定義這種行為!”。可是經過若干萬年之后人類進化了怎么辦?
-
對SuperMan說:“好吧,我幫你定義這種行為,可是一旦Person有了fly,你也必須實現fly”
其實無論上面哪種結果,都相當于接口把實現類綁架了。
【備注】:悄悄地告訴你,上面的代碼是Java語言 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
2、GO語言的接口呢?
GO語言有接口類型(interface{}),它與面向對象的接口含義不同,GO語言的接口類型與數組(array)、切片(slice)、集合(map)、結構體(struct)是同等地位的。怎么理解這句話呢?
我們前面已知道:
| var num int ?// 定義了一個int型變量num |
同理:
| var any interface{}?// 定義了一個接口類型變量any |
從這個角度上看,GO的interface{}與面向對象的接口是不一樣吧。???更加不一樣的是,interface{}是一個任意類型,或者說是萬能類型。
3、GO語言的任意類型
也就是說定義一個變量為interface{}類型,可以把任意的值賦給這個變量,例如:
| var v1 interface{} = 250 ? ? ??// 把int值賦給interface{} var v2 interface{} = "eagle"?// 把string值賦給interface{} var v3 interface{} = &v1 ? ? ?// 把v1的地址賦給interface{} |
當然函數的入參類型也可以是interface{},這樣函數就可以接受任意類型的參數,例如GO語言標準庫fmt中的函數Println()
| func Println(args ...interface{}){ ? ? ? ??// 略 } |
任意類型看起來很爽,可以把任意的值都賦給interface{}類型變量,就像JDK1.4時的Vector,那時候Java還沒有泛型的概念,任意值都可以向Vector里面放,但問題也接踵而至:
| // 定義一個長度為3的Any類型數組,求數組元素之和,即anyArr[0]+anyArr[1]+anyArr[2] var anyArr [3]interface{} anyArr[0] = "eagle" ? ?// anyArr[0]賦值為字符串 anyArr[1] = 20 ? ? ? ? ??// anyArr[1]賦值為int anyArr[2] = 75.3 ? ? ???// anyArr[2]賦值為float64 // 此時若求和,會有什么結果呢?
fmt.Println(anyArr[0] + anyArr[1] + anyArr[2]) |
4、類型判斷
上例直觀上來看,string不能和int直接相加,所以我們需要判斷元素類型,若元素類型是數字型的,我們就執行“+”操作;若元素類型是字符串型的,我們就跳過。這里需要引入另外一個知識:switch-type
即:拿到一個interface{}之后,可以結合switch語句判斷變量的類型 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
例如:
| var v interface{} = 3 switch?v.(type){ ? ? ? case int: ? ? ? ? ? ? ? fmt.Println("3 is int") ? ? ? case string: ? ? ? ? ? ? ? fmt.Println("3 is string") ? ? ? default: ? ? ? ? ? ? ? fmt.Println("unkown type") } |
所以上面的例子可以進一步修改如下:
| // 定義一個長度為3的Any類型數組,求數組元素之和,即anyArr[0]+anyArr[1]+anyArr[2] var anyArr [3]interface{} anyArr[0] = "eagle" anyArr[1] = 20 anyArr[2] = 75.3 // 定義一個總和變量total var total float64 = 0 // 遍歷Any類型數組 for i := 0; i < len(anyArr); i++ { ? ? ? ??// 針對Any類型數組中的每個元素進行類型查詢 ? ? ? ??switch?vType := anyArr[i].(type)?{ ? ? ? ? ? ? ? case?int: ? ? ? ? ? ? ? ? ? ? ? total += float64(vType) ? ? ? ? ? ? ? case?float64: ? ? ? ? ? ? ? ? ? ? ? total += vType ? ? ? ? ? ? ? default: ? ? ? ? ? ? ? ? ? ? ? // do nothing ? ? ? ? } } // 打印Any類型數組中數字之和 fmt.Println(total) |
5、interface類型與struct類型
?從上面看interface類型很簡單嘛,或許吧。
我們再回顧一下struct類型,struct類型是一個結構體,里面可以定義成員,它類似面向對象的一個類,類里面可以定義成員變量,比如:
| // 定義一個person結構體,里面有姓名、年齡、身高、體重成員 type person struct{ ? ? ? ? name ? ? ? ? ? ? ? ? string ? ? ? ? age ? ? ? ? ? ? ? ? ? ?int ? ? ? ? height, weight ? float64 } |
那么interface類型是否也可以這樣定義呢?如下:
| /** ?* 定義一個手表接口,通過手表接口我們可以知道小時、分鐘和秒 ?*/ type watch interface { ? ? ? ? getHour() int ? ? ? ? getMinute() int ? ? ? ? getSecond() int } |
通過編譯(go build myIf.go)會發現并沒有拋出錯誤!
從結果可以看出完全可以這樣定義一個類型為interface的變量watch,并且還可以為watch增加相應的方法;但與struct不同的是:struct里面的成員是變量,而interface里面的成員是函數,即我們可以使用interface定義接口。
6、interface定義接口示例
(1)GO語言接口實現
? ? ? ?周圍的不少朋友現在都有一款iWatch智能手表,一般都用來運動時監控心率,這也意味著iWatch不僅能看時間這么簡單。下面我們定義一個iWatch類型:
| type iWatch int ? ?// 定義一個iWatch類型,它實際上就是int型;相當于為int型取一個別名iWatch |
接下來為iWatch類型增加三個方法,分別為getHour()、getMinute()、getSecond()
| // 為iWatch增加getHour()方法 func (w iWatch) getHour() int { ? ? ? ? return time.Now().Hour() } // 為iWatch增加getMinute()方法 func (w iWatch) getMinute() int { ? ? ? ? return time.Now().Minute() } // 為iWatch增加getSecond()方法 func (w iWatch) getSecond() int { ? ? ? ? return time.Now().Second() } |
下面是GO語言的精彩內容,請各位看客睜大眼睛:
| func main() { ? ? ? ? var w watch ?// 定義類型為watch的變量w ? ? ? ? var t iWatch ?// 定義類型為iWatch的變量t ? ? ? ? w = t ? ? ? ? ? ??// 把類型為watch的變量w賦值給類型為iWatch的變量t,這樣能行的通嗎? ? ? ? ? fmt.Println("Current Hour:", w.getHour(), ", Minute:", w.getMinute(), ", Second:", w.getSecond()) } |
在這個測試代碼中:
var w watch
相當于定義了一個接口變量
var t iWatch
相當于定義了一個iWatch對象
w = t
直接把對象t 賦給了接口變量w,但沒有像其它面向對象語言那樣,讓iWatch?implements?watch,這樣能行的通嗎?
把“嗎”字去掉,請看結果:
神奇吧 :)
以上是GO語言的接口實現。
(2)Java語言的接口實現
用面向對象的編程語言來解釋:
在面向對象的編程語言中,比如Son是一個實現類,Father是一個接口,Son要實現Father接口,必須使用implements顯式地聲明,同時在Son中實現Father里面定義的方法,比如:
interface Father{
? ? ? ? ?getHour();
}
class Son?implements?Father{
? ? ? ? ?// 實現父接口Father定義的方法getHour()
? ? ? ? ?public int getHour(){
? ? ? ? ? ? ? ? ?return 20;
? ? ? ? ?} ? ? ?
}
一旦接口Father增加一個方法getSecond(),那么實現該接口的所有孩兒都必須實現getSecond()方法。在使用時:
Father father = new Son();
即孩兒對象可以賦值給Father接口
【備注】:若對上面面向對象編程語言不熟悉的話,建議看一下設計模式相關的書籍
(3)侵入式接口和非侵入式接口
像上面(2)中的Java就是侵入式接口。
GO語言中,像上面(1)所示,盡管定義了接口watch,但實現類iWatch并沒有顯示地聲明實現該接口,只是watch中的方法都已在iWatch中實現,那么這種父子關系已建立,這種接口被稱為“非侵入式接口”
7、引申
上面例子中,為iWatch定義了三個方法,現在我們修改一下這三個方法:
| // 為*iWatch增加getHour()方法 func (w *iWatch) getHour() int { ? ? ? ? return time.Now().Hour() } // 為*iWatch增加getMinute()方法 func (w *iWatch) getMinute() int { ? ? ? ? return time.Now().Minute() } // 為*iWatch增加getSecond()方法 func (w *iWatch) getSecond() int { ? ? ? ? return time.Now().Second() } |
這相當于并不是為iWatch類型增加了三個方法,而是為*iWatch類型增加了三個方法,那么調用時也需要相應修改:
| func main() { ? ? ? ? var w watch ? ? ? ? var t iWatch ? ? ? ? w = &t ? ??? ? ? ? ? ? fmt.Println("Current Hour:", w.getHour(), ", Minute:", w.getMinute(), ", Second:", w.getSecond()) } |
好了,關于GO語言的接口類型就聊到這里,請記住這么三句話:
-
interface是類型
-
interface類型的變量可以賦值
-
任何實現了interface類型的具體類型變量,都可以賦值給interface類型的變量
本文出自 “青客” 博客,請務必保留此出處http://qingkechina.blog.51cto.com/5552198/1675115
總結
以上是生活随笔為你收集整理的【14】GO语言的接口类型的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CSS学习目录
- 下一篇: REDIS一致性检查