<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
go語言並沒有物件導向的相關概念,go語言提到的介面和java、c++等語言提到的介面不同,它不會顯示的說明實現了介面,沒有繼承、子類、implements關鍵詞。
在 Go 語言中介面包含兩種含義:它既是方法的集合, 同時還是一種型別。在Go 語言中是隱式實現的,意思就是對於一個具體的型別,不需要宣告它實現了哪些介面,只需要提供介面所必需的方法。
go語言通過隱性的方式實現了介面功能,相對比較靈活。
Go語言介面的特點:
每個介面型別由任意個方法簽名組成,介面的定義格式如下:
type 介面型別名 interface{
方法名1( 參數列1 ) 返回值列表1
方法名2( 參數列2 ) 返回值列表2
…
}
說明
er
,如有寫操作的介面叫 Writer
,有字串功能的介面叫 Stringer
,有關閉功能的介面叫 Closer
等。介面名最好要能突出該介面的型別含義。舉個例子,定義一個包含Write
方法的Writer
介面。
type writer interface{ Write([]byte) error }
介面就是規定了一個需要實現的方法列表,在 Go 語言中一個型別只要實現了介面中規定的所有方法,那麼我們就稱它實現了這個介面。
範例
定義的Eater
介面型別,它包含一個Eat
方法。
// Eater 介面 type Eater interface { Eat() }
有一個Dog
結構體型別如下。
type Dog struct {}
因為Eater
介面只包含一個Eat
方法,所以只需要給Dog
結構體新增一個Eat
方法就可以滿足Eater
介面的要求。
//Dog型別的Eat方法 func (d Dog) Eat() { fmt.Println("吃骨頭!") }
這樣就稱為Dog
實現了Eater
介面。
完整程式碼
// Eater 介面 type Eater interface { Eat() } type Dog struct {} //Dog型別的Eat方法 func (d Dog) Eat() { fmt.Println("吃骨頭!") } func main() { dog := Dog{} dog.Eat() }
多數情況下,資料可能包含不同的型別,卻會有一個或者多個共同點,這些共同點就是抽象的基礎。
範例
// Eater 介面 type Eater interface { Eat() } type Dog struct {} //Dog型別的Eat方法 func (d Dog) Eat() { fmt.Println("狗狗喜歡吃骨頭!") } type Cat struct {} func (c Cat) Eat(){ fmt.Println("小貓喜歡吃魚!") } func main() { dog := Dog{} dog.Eat() cat := Cat{} cat.Eat() }
從動物身上,可以抽象出來一個eat
方法,這樣即使在擴充套件其它動物進來,也只需要實現Eater 介面
中的Eat()
方法就可以完成對吃
這個動作的呼叫。
介面可以理解為某一個方面的抽象,可以是多對一的(多個型別實現一個介面),這也是多型的體現。
一個介面型別的變數能夠儲存所有實現了該介面的型別變數。
例如在上面的範例中,Dog
和Cat
型別均實現了Eater
介面,此時一個Eater
型別的變數就能夠接收Cat
和Dog
型別的變數。
var x Eater // 宣告一個Eater型別的變數x a := Cat{} // 宣告一個Cat型別變數a b := Dog{} // 宣告一個Dog型別變數b x = a // 可以把Cat型別變數直接賦值給x x.Eat() // 小貓喜歡吃魚! x = b // 可以把Dog型別變數直接賦值給x x.Eat() // 狗狗喜歡吃骨頭!
通過下方一個範例來演示實現介面使用值接收者和使用指標接收者有什麼區別。
定義一個Mover
介面,它包含一個Move
方法。
// Mover 定義一個介面型別 type Mover interface { Move() }
我們定義一個Dog
結構體型別,並使用值接收者為其定義一個Move
方法。
// Dog 狗結構體型別 type Dog struct{} // Move 使用值接收者定義Move方法實現Mover介面 func (d Dog) Move() { fmt.Println("狗會動") }
此時實現Mover
介面的是Dog
型別。
var x Mover // 宣告一個Mover型別的變數x var d1 = Dog{} // d1是Dog型別 x = d1 // 可以將d1賦值給變數x x.Move() var d2 = &Dog{} // d2是Dog指標型別 x = d2 // 也可以將d2賦值給變數x x.Move()
從上面的程式碼中我們可以發現,使用值接收者實現介面之後,不管是結構體型別還是對應的結構體指標型別的變數都可以賦值給該介面變數。
我們再來測試一下使用指標接收者實現介面有什麼區別。
// Cat 貓結構體型別 type Cat struct{} // Move 使用指標接收者定義Move方法實現Mover介面 func (c *Cat) Move() { fmt.Println("貓會動") }
此時實現Mover
介面的是*Cat
型別,我們可以將*Cat
型別的變數直接賦值給Mover
介面型別的變數x
。
var c1 = &Cat{} // c1是*Cat型別 x = c1 // 可以將c1當成Mover型別 x.Move()
但是不能給將Cat
型別的變數賦值給Mover
介面型別的變數x
。
// 下面的程式碼無法通過編譯 var c2 = Cat{} // c2是Cat型別 x = c2 // 不能將c2當成Mover型別
由於Go語言中有對指標求值的語法糖,對於值接收者實現的介面,無論使用值型別還是指標型別都沒有問題。但是我們並不總是能對一個值求址,所以對於指標接收者實現的介面要額外注意。
一個型別可以同時實現多個介面,而介面間彼此獨立,不知道對方的實現。
範例
動物不僅有吃的屬性,還有動的屬性,可以通過定義兩個介面,讓同一個動物分別實現這兩種屬性
// Eater 介面 type Eater interface { Eat() } // Mover 介面 type Mover interface { Move() } type Dog struct {} //Dog型別的Eat方法 func (d Dog) Eat() { fmt.Println("狗狗喜歡吃骨頭!") } //Dog型別的Move方法 func (d Dog) Move(){ fmt.Println("狗狗喜歡玩耍!") } func main() { //初始化結構體 dog := Dog{} //dog實現了Eater和Mover兩個介面 eat := dog move := dog eat.Eat() //對Eater型別呼叫Eat方法 move.Move() //對Mover型別呼叫Move方法 }
程式中的結構體Dog
分別實現了Eater和Mover兩個介面中的方法。
Go語言中不同的型別還可以實現同一介面。
一個介面的所有方法,不一定需要由一個型別完全實現,介面的方法可以通過在型別中嵌入其他型別或者結構體來實現。
// WashingMachine 洗衣機 type WashingMachine interface { wash() dry() } // 甩幹器 type dryer struct{} // 實現WashingMachine介面的dry()方法 func (d dryer) dry() { fmt.Println("甩一甩") } // 洗衣機 type haier struct { dryer //嵌入甩幹器 } // 實現WashingMachine介面的wash()方法 func (h haier) wash() { fmt.Println("洗刷刷") } func main() { h := haier{} h.dry() h.wash() }
介面與介面之間可以通過互相巢狀形成新的介面型別。例如Go標準庫io
原始碼中就有很多介面之間互相組合的範例。
// src/io/io.go type Reader interface { Read(p []byte) (n int, err error) } type Writer interface { Write(p []byte) (n int, err error) } type Closer interface { Close() error } // ReadWriter 是組合Reader介面和Writer介面形成的新介面型別 type ReadWriter interface { Reader Writer } // ReadCloser 是組合Reader介面和Closer介面形成的新介面型別 type ReadCloser interface { Reader Closer } // WriteCloser 是組合Writer介面和Closer介面形成的新介面型別 type WriteCloser interface { Writer Closer }
對於這種由多個介面型別組合形成的新介面型別,同樣只需要實現新介面型別中規定的所有方法就算實現了該介面型別。
介面也可以作為結構體的一個欄位,我們來看一段Go標準庫sort
原始碼中的範例。
// src/sort/sort.go // Interface 定義通過索引對元素排序的介面型別 type Interface interface { Len() int Less(i, j int) bool Swap(i, j int) } // reverse 結構體中嵌入了Interface介面 type reverse struct { Interface }
通過在結構體中嵌入一個介面型別,從而讓該結構體型別實現了該介面型別,並且還可以改寫該介面的方法。
// Less 為reverse型別新增Less方法,重寫原Interface介面型別的Less方法 func (r reverse) Less(i, j int) bool { return r.Interface.Less(j, i) }
Interface
型別原本的Less
方法簽名為Less(i, j int) bool
,此處重寫為r.Interface.Less(j, i)
,即通過將索引引數交換位置實現反轉。
在這個範例中還有一個需要注意的地方是reverse
結構體本身是不可匯出的(結構體型別名稱首字母小寫),sort.go
中通過定義一個可匯出的Reverse
函數來讓使用者建立reverse
結構體範例。
func Reverse(data Interface) Interface { return &reverse{data} }
這樣做的目的是保證得到的reverse
結構體中的Interface
屬性一定不為nil
,否者r.Interface.Less(j, i)
就會出現空指標panic。
Golang 中的介面可以不定義任何方法,沒有定義任何方法的介面就是空介面。空介面表示沒有任何約束,因此任何型別變數都可以實現空介面。
空介面在實際專案中用的是非常多的,用空介面可以表示任意資料型別。
範例
func main() { //定義一個空介面x,x變數可以接收任意的資料型別 var x interface{} str := "Hello Go" x = str fmt.Printf("type:%T,value:%vn",x,x) num := 10 x = num fmt.Printf("type:%T,value:%vn",x,x) bool := true x = bool fmt.Printf("type:%T,value:%vn",x,x) }
執行結果
type:string,value:Hello Go
type:int,value:10
type:bool,value:true
1、空介面作為函數的引數
// 空介面作為函數引數 func show(a interface{}) { fmt.Printf("type:%T value:%vn", a, a) } func main() { show(1) show(true) show(3.14) var mapStr = make(map[string]string) mapStr["name"] = "Leefs" mapStr["age"] = "12" show(mapStr) }
執行結果
type:int value:1
type:bool value:true
type:float64 value:3.14
type:map[string]string value:map[age:12 name:Leefs]
2、map的值實現空介面
func main() { // 空介面作為 map 值 var studentInfo = make(map[string]interface{}) studentInfo["name"] = "Jeyoo" studentInfo["age"] = 18 studentInfo["married"] = false fmt.Println(studentInfo) }
執行結果
map[age:18 married:false name:Jeyoo]
3、切片實現空介面
func main() { var slice = []interface{}{"Jeyoo", 20, true, 32.2} fmt.Println(slice) }
執行結果
[Jeyoo 20 true 32.2]
一個介面的值(簡稱介面值)是由一個具體型別和具體型別的值兩部分組成的。這兩部分分別稱為介面的動態型別和動態值。
如果我們想要判斷空介面中值的型別,那麼這個時候就可以使用型別斷言,其語法格式:
x.(T)
說明
該語法返回兩個引數,第一個引數是 x 轉化為 T 型別後的變數,第二個值是一個布林值,若為 true 則表示斷言成功,為 false 則表示斷言失敗。
範例
func main() { var x interface{} x = "Hello GO" v, ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println("型別斷言失敗") } }
上面的範例中如果要斷言多次就需要寫多個 if 判斷,這個時候我們可以使用 switch 語句來 實現:
注意:型別.(type)只能結合 switch 語句使用
// justifyType 對傳入的空介面型別變數x進行型別斷言 func justifyType(x interface{}) { switch v := x.(type) { case string: fmt.Printf("x is a string,value is %vn", v) case int: fmt.Printf("x is a int is %vn", v) case bool: fmt.Printf("x is a bool is %vn", v) default: fmt.Println("unsupport type!") } }
由於介面型別變數能夠動態儲存不同型別值的特點,所以很多初學者會濫用介面型別(特別是空介面)來實現編碼過程中的便捷。
只有當有兩個或兩個以上的具體型別必須以相同的方式進行處理時才需要定義介面。切記不要為了使用介面型別而增加不必要的抽象,導致不必要的執行時損耗。
在 Go 語言中介面是一個非常重要的概念和特性,使用介面型別能夠實現程式碼的抽象和解耦,也可以隱藏某個功能的內部實現,但是缺點就是在檢視原始碼的時候,不太方便查詢到具體實現介面的型別。
相信很多讀者在剛接觸到介面型別時都會有很多疑惑,請牢記介面是一種型別,一種抽象的型別。區別於我們在之前章節提到的那些具體型別(整型、陣列、結構體型別等),它是一個只要求實現特定方法的抽象型別。
以上就是Golang介面使用教學詳解的詳細內容,更多關於Golang介面的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45