https://blog.csdn.net/u011957758/article/details/81150622
前言
接口在面向對象編程中是經常使用的招式,也是體現多態很重要的手段。
是的。Golang中也有接口這玩意兒。
本文將以通俗易懂的方式,說清楚Golang的接口。
10s後,以下知識點即將到達戰場:
1.爲什麼需要接口?
2.接口是什麼?如何定義?
3.接口實戰初體驗
4.如何測試是否已實現該接口?
5.空接口&類型斷言
6.接口零值
7.一個類型實現多個接口
8.指針與值類型實現接口的區別
9.接口嵌套
正文
1.爲什麼需要接口?
多數情況下,數據可能包含不同的類型,卻會有一個或者多個共同點,這些共同點就是抽象的基礎。前文講到的Golang繼承解決的是is-a的問題,單一繼承的關係。但是當不同的父類具有相同的行爲的時候,單一繼承就沒法解決了。
於是乎,接口出現了。接口可以理解爲某一個方面的抽象,可以是多對一的(多個類型實現一個接口),這也是多態的體現。解決了上文一對一的問題。
2.接口是什麼?如何定義?
是什麼
接口是一組僅包含方法名、參數、返回值的未具體實現的方法的集合。
如果實現了接口的所有方法,則認爲實現了該接口,無需在該類型上顯示的添加聲明。
這個解釋下,加深印象,在php中接口是長這樣的:
//定義接口
interface base{
public function getName();
}
//學生類
class student implements base{
public function getName(){
echo "咖啡色的羊駝";
}
}
這裏有個關鍵字:implements。
這樣的聲明稱之爲顯示的,而在Golang中接口是隱式地實現。(埋個伏筆看下文)
定義
type interfaceName interface {
// 方法列表
GetName() string
}
3.接口實戰初體驗
實際編程中呢,接口的命名大夥兒喜歡使用er結尾。當然這個看個人喜好。
上代碼:
package main
import (
"fmt"
)
// 定義一個接口
type People interface {
ReturnName() string
}
// 定義一個結構體
type Student struct {
Name string
}
// 定義結構體的一個方法。
// 突然發現這個方法同接口People的所有方法(就一個),此時可直接認爲結構體Student實現了接口People
func (s Student) ReturnName() string {
return s.Name
}
func main() {
cbs := Student{Name:"咖啡色的羊駝"}
var a People
// 因爲Students實現了接口所以直接賦值沒問題
// 如果沒實現會報錯:cannot use cbs (type Student) as type People in assignment:Student does not implement People (missing ReturnName method)
a = cbs
name := a.ReturnName()
fmt.Println(name) // 輸出"咖啡色的羊駝"
}
4.如何測試是否已實現該接口?
使用接口特有的斷言判斷來實現(下文還會再次提到,加深印象)。
語法:x.(T)
這樣的語法只適應於x是interface類型
接着上文例子,繼續上代碼:
// 由於x.(T)只能是接口類型判斷,所以傳參時候,傳入的是接口類型
// 爲何test的類型可以是一個空接口?埋伏筆下文便知。
func CheckPeople(test interface{}) {
if _, ok := test.(People); ok {
fmt.Printf("Student implements People")
}
}
func main() {
cbs := Student{Name:"咖啡色的羊駝"}
CheckPeople(cbs) // Student implements People
}
5.空接口&類型斷言
空接口
空接口就是不包含任何方法的接口。正因爲如此,所有的類型都實現了空接口。
雖然空接口起不到任何作用,但是空接口在需要存儲任何類型數值的時候非常有用,這也回答了上文的問題,因爲空接口可以存儲任意類型的數據。
// 定義cbs爲空接口
var cbs interface{}
var i int = 5
var s string = "Hello world"
// cbs可以存儲任意類型的數值
cbs = i
cbs = s
類型斷言
既然空接口可以存儲任意類型,那麼如何區分不同的類型?
常用的有兩種方法:Comma-ok斷言、switch判斷。
上代碼:
package main
import (
"fmt"
)
// 定義一個結構體
type Student struct {
Name string
}
// 類型斷言
func main() {
Params := make([]interface{}, 3)
Params[0] = 88 // 整型
Params[1] = "咖啡色的羊駝" // 字符串
Params[2] = Student{Name: "cbs"} // 自定義結構體類型
// Comma-ok斷言
for index, v := range Params {
if _, ok := v.(int); ok {
fmt.Printf("Params[%d] 是int類型 \n", index)
} else if _, ok := v.(string); ok {
fmt.Printf("Params[%d] 是字符串類型\n", index)
} else if _, ok := v.(Student); ok {
fmt.Printf("Params[%d] 是自定義結構體類型\n", index)
} else {
fmt.Printf("list[%d] 未知類型\n", index)
}
}
// switch判斷
for index, v := range Params {
switch value := v.(type) {
case int:
fmt.Printf("Params[%d] 是int類型, 值:%d \n", index,value)
case string:
fmt.Printf("Params[%d] 是字符串類型, 值:%s\n", index,value)
case Student:
fmt.Printf("Params[%d] 是Person類型, 值:%s\n", index,value)
default:
fmt.Printf("list[%d] 未知類型\n", index)
}
}
}
6.接口零值
接口的零值是nil
package main
import (
"fmt"
)
type People interface {
GetName() string
}
// 輸出 "cbs is nil 類型"
func main() {
var cbs People
if cbs == nil {
fmt.Println("cbs is nil 類型")
}
}
7.一個類型實現多個接口
package main
import (
"fmt"
)
type People interface {
ReturnName() string
}
type Role interface {
ReturnRole() string
}
type Student struct {
Name string
}
func (s Student) ReturnName() string {
return s.Name
}
func (s Student) ReturnRole() string {
return "學生"
}
func main() {
cbs := Student{Name: "咖啡色的羊駝"}
var a People // 定義a爲People接口類型
var b Role // 定義b爲Role接口類型
a = cbs // 由於Student實現了People所有方法,所以接口實現成功,可直接賦值
b = cbs // 由於Student實現了Role所有方法,所以接口實現成功,可直接賦值
name := a.ReturnName()
fmt.Println(name) // 輸出"咖啡色的羊駝"
role := b.ReturnRole()
fmt.Println(role) // 輸出"學生"
}
也說明一個東西:實現了某個接口的類型,還可以有其它的方法。只要是方法實現包含接口的即可。
8.指針與值類型實現接口的區別
package main
import (
"fmt"
)
type People interface {
ReturnName() string
}
type Student struct {
Name string
}
type Teacher struct {
Name string
}
func (s Student) ReturnName() string {
return s.Name
}
func (t *Teacher) ReturnName() string {
return t.Name
}
func main() {
cbs := Student{Name: "咖啡色的羊駝"}
sss := Teacher{Name: "咖啡色的羊駝的老師"}
// 值類型
var a People
a = cbs
name := a.ReturnName()
fmt.Println(name)
// 指針類型
// a = sss <- 這樣寫不行!!!
a = &sss // 由於是指針類型,所以賦值的時候需要加上&
name = a.ReturnName()
fmt.Println(name) // 輸出"咖啡色的羊駝的老師"
}
“a = sss”這樣寫會發生報錯:
cannot use sss (type Teacher) as type People in assignment:
Teacher does not implement People (ReturnName method has pointer receiver)
因爲是Teacher的指針實現了ReturnName方法,Teacher本身沒實現。
9.接口嵌套
類似於PHP的接口繼承,Golang也有它的接口嵌套。
package main
import (
"fmt"
)
type People interface {
ReturnName() string
}
type Role interface {
People // 接口嵌套
ReturnRole() string
}
type Student struct {
Name string
}
func (s Student) ReturnName() string {
return s.Name
}
func (s Student) ReturnRole() string {
return "學生"
}
func main() {
cbs := Student{Name: "咖啡色的羊駝"}
var a Role
a = cbs
name := a.ReturnName()
fmt.Println(name)
role := a.ReturnRole()
fmt.Println(role)
}