接口(interface)定義了一個對象的行爲規範,只定義規範不實現,由具體的對象來實現規範的細節。程序員
在Go語言中接口(interface)是一種類型,一種抽象的類型。面試
interface
是一組method
的集合,是duck-type programming
的一種體現。接口作的事情就像是定義一個協議(規則),只要一臺機器有洗衣服和甩乾的功能,我就稱它爲洗衣機。不關心屬性(數據),只關心行爲(方法)。編程
爲了保護你的Go語言職業生涯,請牢記接口(interface)是一種類型。微信
type Cat struct{} func (c Cat) Say() string { return "喵喵喵" } type Dog struct{} func (d Dog) Say() string { return "汪汪汪" } func main() { c := Cat{} fmt.Println("貓:", c.Say()) d := Dog{} fmt.Println("狗:", d.Say()) }
上面的代碼中定義了貓和狗,而後它們都會叫,你會發現main函數中明顯有重複的代碼,若是咱們後續再加上豬、青蛙等動物的話,咱們的代碼還會一直重複下去。那咱們能不能把它們當成「能叫的動物」來處理呢?函數
像相似的例子在咱們編程過程當中會常常遇到:測試
好比一個網上商城可能使用支付寶、微信、銀聯等方式去在線支付,咱們能不能把它們當成「支付方式」來處理呢?ui
好比三角形,四邊形,圓形都能計算周長和麪積,咱們能不能把它們當成「圖形」來處理呢?設計
好比銷售、行政、程序員都能計算月薪,咱們能不能把他們當成「員工」來處理呢?3d
Go語言中爲了解決相似上面的問題,就設計了接口這個概念。接口區別於咱們以前全部的具體類型,接口是一種抽象的類型。當你看到一個接口類型的值時,你不知道它是什麼,惟一知道的是經過它的方法能作什麼。指針
Go語言提倡面向接口編程。
每一個接口由數個方法組成,接口的定義格式以下:
type 接口類型名 interface{ 方法名1( 參數列表1 ) 返回值列表1 方法名2( 參數列表2 ) 返回值列表2 … }
其中:
type
將接口定義爲自定義的類型名。Go語言的接口在命名時,通常會在單詞後面添加er
,若有寫操做的接口叫Writer
,有字符串功能的接口叫Stringer
等。接口名最好要能突出該接口的類型含義。舉個例子:
type writer interface{ Write([]byte) error }
當你看到這個接口類型的值時,你不知道它是什麼,惟一知道的就是能夠經過它的Write方法來作一些事情。
一個對象只要所有實現了接口中的方法,那麼就實現了這個接口。換句話說,接口就是一個須要實現的方法列表。
咱們來定義一個Sayer
接口:
// Sayer 接口 type Sayer interface { say() }
定義dog
和cat
兩個結構體:
type dog struct {} type cat struct {}
由於Sayer
接口裏只有一個say
方法,因此咱們只須要給dog
和cat
分別實現say
方法就能夠實現Sayer
接口了。
// dog實現了Sayer接口 func (d dog) say() { fmt.Println("汪汪汪") } // cat實現了Sayer接口 func (c cat) say() { fmt.Println("喵喵喵") }
接口的實現就是這麼簡單,只要實現了接口中的全部方法,就實現了這個接口。
那實現了接口有什麼用呢?
接口類型變量可以存儲全部實現了該接口的實例。 例如上面的示例中,Sayer
類型的變量可以存儲dog
和cat
類型的變量。
func main() { var x Sayer // 聲明一個Sayer類型的變量x a := cat{} // 實例化一個cat b := dog{} // 實例化一個dog x = a // 能夠把cat實例直接賦值給x x.say() // 喵喵喵 x = b // 能夠把dog實例直接賦值給x x.say() // 汪汪汪 }
使用值接收者實現接口和使用指針接收者實現接口有什麼區別呢?接下來咱們經過一個例子看一下其中的區別。
咱們有一個Mover
接口和一個dog
結構體。
type Mover interface { move() } type dog struct {}
func (d dog) move() { fmt.Println("狗會動") }
此時實現接口的是dog
類型:
func main() { var x Mover var wangcai = dog{} // 旺財是dog類型 x = wangcai // x能夠接收dog類型 var fugui = &dog{} // 富貴是*dog類型 x = fugui // x能夠接收*dog類型 x.move() }
從上面的代碼中咱們能夠發現,使用值接收者實現接口以後,不論是dog結構體仍是結構體指針dog類型的變量均可以賦值給該接口變量。由於Go語言中有對指針類型變量求值的語法糖,dog指針fugui
內部會自動求值fugui
。
一樣的代碼咱們再來測試一下使用指針接收者有什麼區別:
func (d *dog) move() { fmt.Println("狗會動") } func main() { var x Mover var wangcai = dog{} // 旺財是dog類型 x = wangcai // x不能夠接收dog類型 var fugui = &dog{} // 富貴是*dog類型 x = fugui // x能夠接收*dog類型 }
此時實現Mover
接口的是*dog
類型,因此不能給x
傳入dog
類型的wangcai,此時x只能存儲*dog
類型的值。
請問下面的代碼是否能經過編譯?
type People interface { Speak(string) string } type Student struct{} func (stu *Stduent) Speak(think string) (talk string) { if think == "sb" { talk = "你是個大帥比" } else { talk = "您好" } return } func main() { var peo People = Student{} think := "bitch" fmt.Println(peo.Speak(think)) }
一個類型能夠同時實現多個接口,而接口間彼此獨立,不知道對方的實現。 例如,狗能夠叫,也能夠動。咱們就分別定義Sayer接口和Mover接口,以下: Mover
接口。
// Sayer 接口 type Sayer interface { say() } // Mover 接口 type Mover interface { move() }
dog既能夠實現Sayer接口,也能夠實現Mover接口。
type dog struct { name string } // 實現Sayer接口 func (d dog) say() { fmt.Printf("%s會叫汪汪汪\n", d.name) } // 實現Mover接口 func (d dog) move() { fmt.Printf("%s會動\n", d.name) } func main() { var x Sayer var y Mover var a = dog{name: "旺財"} x = a y = a x.say() y.move() }
Go語言中不一樣的類型還能夠實現同一接口 首先咱們定義一個Mover
接口,它要求必須由一個move
方法。
// Mover 接口 type Mover interface { move() }
例如狗能夠動,汽車也能夠動,可使用以下代碼實現這個關係:
type dog struct { name string } type car struct { brand string } // dog類型實現Mover接口 func (d dog) move() { fmt.Printf("%s會跑\n", d.name) } // car類型實現Mover接口 func (c car) move() { fmt.Printf("%s速度70邁\n", c.brand) }
這個時候咱們在代碼中就能夠把狗和汽車當成一個會動的物體來處理了,再也不須要關注它們具體是什麼,只須要調用它們的move
方法就能夠了。
func main() { var x Mover var a = dog{name: "旺財"} var b = car{brand: "保時捷"} x = a x.move() x = b x.move() }
上面的代碼執行結果以下:
旺財會跑 保時捷速度70邁
而且一個接口的方法,不必定須要由一個類型徹底實現,接口的方法能夠經過在類型中嵌入其餘類型或者結構體來實現。
// 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("洗刷刷") }
接口與接口間能夠經過嵌套創造出新的接口。
// Sayer 接口 type Sayer interface { say() } // Mover 接口 type Mover interface { move() } // 接口嵌套 type animal interface { Sayer Mover }
嵌套獲得的接口的使用與普通接口同樣,這裏咱們讓cat實現animal接口:
type cat struct { name string } func (c cat) say() { fmt.Println("喵喵喵") } func (c cat) move() { fmt.Println("貓會動") } func main() { var x animal x = cat{name: "花花"} x.move() x.say() }
空接口是指沒有定義任何方法的接口。所以任何類型都實現了空接口。
空接口類型的變量能夠存儲任意類型的變量。
func main() { // 定義一個空接口x var x interface{} s := "Hello 沙河" x = s fmt.Printf("type:%T value:%v\n", x, x) i := 100 x = i fmt.Printf("type:%T value:%v\n", x, x) b := true x = b fmt.Printf("type:%T value:%v\n", x, x) }
使用空接口實現能夠接收任意類型的函數參數。
// 空接口做爲函數參數 func show(a interface{}) { fmt.Printf("type:%T value:%v\n", a, a) }
使用空接口實現能夠保存任意值的字典。
// 空接口做爲map值 var studentInfo = make(map[string]interface{}) studentInfo["name"] = "沙河娜扎" studentInfo["age"] = 18 studentInfo["married"] = false fmt.Println(studentInfo)
空接口能夠存儲任意類型的值,那咱們如何獲取其存儲的具體數據呢?
一個接口的值(簡稱接口值)是由一個具體類型
和具體類型的值
兩部分組成的。這兩部分分別稱爲接口的動態類型
和動態值
。
咱們來看一個具體的例子:
var w io.Writer w = os.Stdout w = new(bytes.Buffer) w = nil
想要判斷空接口中的值這個時候就可使用類型斷言,其語法格式:
x.(T)
其中:
interface{}
的變量x
多是的類型。該語法返回兩個參數,第一個參數是x
轉化爲T
類型後的變量,第二個值是一個布爾值,若爲true
則表示斷言成功,爲false
則表示斷言失敗。
舉個例子:
func main() { var x interface{} x = "Hello 沙河" v, ok := x.(string) if ok { fmt.Println(v) } else { fmt.Println("類型斷言失敗") } }
上面的示例中若是要斷言屢次就須要寫多個if
判斷,這個時候咱們可使用switch
語句來實現:
func justifyType(x interface{}) { switch v := x.(type) { case string: fmt.Printf("x is a string,value is %v\n", v) case int: fmt.Printf("x is a int is %v\n", v) case bool: fmt.Printf("x is a bool is %v\n", v) default: fmt.Println("unsupport type!") } }
由於空接口能夠存儲任意類型值的特色,因此空接口在Go語言中的使用十分普遍。
關於接口須要注意的是,只有當有兩個或兩個以上的具體類型必須以相同的方式進行處理時才須要定義接口。不要爲了接口而寫接口,那樣只會增長沒必要要的抽象,致使沒必要要的運行時損耗。