7. 接口
7.1 什麼是接口
接口是一組契約(或者稱爲規定,合約,行爲以及方法等等)。這就好比家裏的插座接口,有些插座的插座的接口可以插入不同的插頭。
7.2 接口定義與格式
接口(interface)是一種類型,用來定義行爲(方法)。這句話有兩個重點,類型和定義行爲。
首先解釋定義行爲:
接口即一組方法定義的集合,定義了對象的一組行爲,就是定義了一些函數,由具體的類型實例實現具體的方法。
換句話說,一個接口就是定義(規範或約束),接口並不會實現這些方法,具體的實現由類實現,實現接口的類必須嚴格按照接口的聲明來實現接口提供的所有功能。
爲什麼要有接口?
接口的作用應該是將定義與實現分離,降低耦合度。
在多人合作開發同一個項目時,接口表示調用者和設計者的一種約定,事先定義好相互調用的接口可以大大提高開發的效率。有了接口,就可以在不影響現有接口聲明的情況下,修改接口的內部實現,從而使兼容性問題最小化。
接口的定義格式:
type Namer interface {
Method1(param_list) return_type //方法名(參數列表) 返回值列表
Method2(param_list) return_type //方法名(參數列表) 返回值列表
}
7.3 接口的實現和實現的條件
怎麼實現接口:
實現接口的類(結構體或者函數,並不一定是結構體)並不需要顯式聲明,只需要實現接口所有的函數就表示實現了該接口,而且類還可以擁有自己的方法。
Go 語言的接口是隱式的,只要類上定義的方法在形式上(名稱、參數和返回值)和接口定義的一樣,那麼這個類自動實現了這個接口,我們就可以使用這個接口變量來指向這個類對象。
接口能被哪些類型實現:
接口可以被結構體實現,也可以被函數類型實現,當然也可以使用自定義。
接口被實現的條件:
接口被實現的條件一:接口的方法與實現接口的類型方法格式一致(方法名、參數類型、返回值類型一致)。
接口被實現的條件二:接口中所有方法均被實現。
結構體以及自定義類型接口的實現:
package main
import "fmt"
type Calculator interface {
Add(x int64)
Sub(x int64)
}
type myInt struct {
data int64
}
func (p *myInt) Add(x int64) {
p.data += x
}
func (p *myInt) Sub(x int64) {
p.data -= x
}
// 結構體也可以擁有自己的方法,這個方法並沒有在接口中
func (p *myInt) Value() int64 {
return p.data
}
type myInt2 int64
func (p *myInt2) Add(x int64) {
*p += myInt2(x)
}
func (p *myInt2) Sub(x int64) {
*p -= myInt2(x)
}
func main() {
var cal Calculator // 接口
p1 := myInt{100}
cal = &p1 // 由於實現的是*myInt的方法,所以需要取址
cal.Add(10)
cal.Sub(2)
// cal.Value() // 錯誤,接口沒有實現
fmt.Println(p1.Value()) // 108(100+10-2)
// 試試不通過接口來操作
p1.Add(10)
p1.Sub(2)
fmt.Println(p1.data) // 116(108+10-2)
// 自定義的類型,並非結構體
p2 := myInt2(20)
cal = &p2
cal.Add(10)
cal.Sub(2)
fmt.Println(p2) // 28(20+10-2)
}
函數體的接口實現:
package main
import (
"fmt"
)
// 調用器接口
type Invoker interface {
// 需要實現一個Call方法
Call(interface{})
}
// 結構體類型
type Struct struct {
}
// 實現Invoker的Call
func (s *Struct) Call(p interface{}) {
fmt.Println("from struct", p)
}
// 函數定義爲類型
type FuncCaller func(interface{})
// 實現Invoker的Call
func (f FuncCaller) Call(p interface{}) {
// 調用f函數本體
f(p)
}
func main() {
// 聲明接口變量
var invoker Invoker
// 實例化結構體
s := new(Struct)
// 將實例化的結構體賦值到接口
invoker = s
// 使用接口調用實例化結構體的方法Struct.Call
invoker.Call("hello")
// 將匿名函數轉爲FuncCaller類型,再賦值給接口
invoker = FuncCaller(func(v interface{}) {
fmt.Println("from function", v)
})
// 使用接口調用FuncCaller.Call,內部會調用函數本體
invoker.Call("hello")
}
總結:只要使用了type定義了一個新的類型,並且實現的接口的所有方法,那麼這個類型就可以賦值給這個接口。還有,如果想要對類型的一些成員進行修改,那麼應該使用指針作爲接收器來實現其方法。
7.4 接口賦值
現在來解釋接口是一個類型,本質是一個指針類型,那麼什麼樣的值可以賦值給接口,有兩種:實現了該接口的類或者接口。
1.將對象賦值給接口
當接口實例中保存了自定義類型的實例後,就可以直接從接口上調用它所保存的實例的方法。
package main
import (
"fmt"
)
//定義接口
type Testinterface interface{
Teststring() string
Testint() int
}
//定義結構體
type TestMethod struct{
name string
age int
}
//結構體的兩個方法隱式實現接口
func (t *TestMethod)Teststring() string{
return t.name
}
func (t *TestMethod)Testint() int{
return t.age
}
func main(){
T1 := &TestMethod{"ling",34}
T2 := TestMethod{"gos",43}
//接口本質是一種類型
//接口賦值:只要類實現了該接口的所有方法,即可將該類賦值給這個接口
var Test1 Testinterface //接口只能是值類型
Test1 = T1 //TestMethod類的指針類型實例傳給接口
fmt.Println(Test1.Teststring())
fmt.Println(Test1.Testint())
Test2 := T2 //TestMethod類的值類型實例傳給接口
fmt.Println(Test2.Teststring())
fmt.Println(Test2.Testint())
}
2.將接口賦值給另一個接口
1.只要兩個接口擁有相同的方法列表(與次序無關),即是兩個相同的接口,可以相互賦值
2.接口賦值只需要接口A的方法列表是接口B的子集(即假設接口A中定義的所有方法,都在接口B中有定義),那麼B接口的實例可以賦值給A的對象。反之不成立,即子接口B包含了父接口A,因此可以將子接口的實例賦值給父接口。
3.即子接口實例實現了子接口的所有方法,而父接口的方法列表是子接口的子集,則子接口實例自然實現了父接口的所有方法,因此可以將子接口實例賦值給父接口。
3.接口類型作爲參數
第一點已經說了可以將實現接口的類賦值給接口,而將接口類型作爲參數很常見。這時,那些實現接口的實例都能作爲接口類型參數傳遞給函數/方法。
package main
import (
"fmt"
)
//Shaper接口
type Shaper interface {
Area() float64
}
// Circle struct結構體
type Circle struct {
radius float64
}
// Circle類型實現Shaper中的方法Area()
func (c *Circle) Area() float64 {
return 3.14 * c.radius * c.radius
}
func main() {
// Circle的指針類型實例
c1 := new(Circle)
c1.radius = 2.5
//將 Circle的指針類型實例c1傳給函數myArea,接收類型爲Shaper接口
myArea(c1)
}
func myArea(n Shaper) {
fmt.Println(n.Area())
}
7.5 空接口
空接口是指沒有定義任何接口方法的接口。沒有定義任何接口方法,意味着Go中的任意對象都已經實現空接口(因爲沒方法需要實現),只要實現接口的對象都可以被接口保存,所以任意對象都可以保存到空接口實例變量中。
空接口的定義方式:
type empty_int interface{}
更常見的,會直接使用interface{}
作爲一種類型,表示空接口。例如:
var i interface{}
再比如函數使用空接口類型參數:
func myfunc(i interface{})
如何使用空接口
可以定義一個空接口類型的array、slice、map、struct等,這樣它們就可以用來存放任意類型的對象,因爲任意類型都實現了空接口。
package main
import "fmt"
func main() {
any := make([]interface{}, 5)
any[0] = 11
any[1] = "hello world"
any[2] = []int{11, 22, 33, 44}
for _, value := range any {
fmt.Println(value)
}
}
結果:
11
hello world
[11 22 33 44]
<nil>
<nil>
通過空接口類型,Go也能像其它動態語言一樣,在數據結構中存儲任意類型的數據。
7.6 接口嵌套
接口可以嵌套,嵌套的內部接口將屬於外部接口,內部接口的方法也將屬於外部接口。
另外在類型嵌套時,如果內部類型實現了接口,那麼外部類型也會自動實現接口,因爲內部屬性是屬於外部屬性的。
type ReadWrite interface {
Read(b Buffer) bool
Write(b Buffer) bool
}
type Lock interface {
Lock()
Unlock()
}
type File interface {<br> //ReadWrite爲內部接口
ReadWrite<br> //Lock爲內部接口
Lock
Close()
}
7.7 類型斷言
類型斷言爲判斷一個類型有沒有實現接口。
假如我現在寫了一個結構體類型 MyFile
來實現上面的 File
接口,那麼我如何知道 MyFile
是否實現了 File
接口呢?
package main
import "fmt"
type MyFile struct{}
func (m *MyFile) Read() bool {
fmt.Printf("Read()\n")
return true
}
// ...
// 假設我這裏相繼實現了 Write(), Lock(),Unlock() 和 Close() 方法
func main() {
my := new(MyFile)
fIntf := File(my)
// 看這裏,看這裏
if v, ok := fIntf.(*MyFile); ok {
v.Read()
}
}
類型斷言的格式:
if v, ok : = varI.(T) ; ok { <br> // checked type assertion<br> //do something
return
}
如果 v
是 varI
轉換到類型 T
的值,ok
會是 true
;否則 v
是類型 T
的零值,ok
是 false
。
要是多個類型實現了同一個接口,比如前面的 areaIntf
,要如何測試呢?
那就要用 type-switch
來判斷了。
switch t := areaIntf.(type) {
case *Rectangle:
// do something
case *Triangle:
// do something
default:
// do something
}
7.8 多態
1、多個類型(結構體)可以實現同一個接口。
2、一個類型(結構體)可以實現多個接口。
3、實現接口的類(結構體)可以賦值給接口。
package main
import "fmt"
type Shaper interface {
Area() float64
}
// ==== Rectangle ====
type Rectangle struct {
length float64
width float64
}
// 實現 Shaper 接口中的方法
func (r *Rectangle) Area() float64 {
return r.length * r.width
}
// Set 是屬於 Rectangle 自己的方法
func (r *Rectangle) Set(l float64, w float64) {
r.length = l
r.width = w
}
// ==== Triangle ====
type Triangle struct {
bottom float64
hight float64
}
func (t *Triangle) Area() float64 {
return t.bottom * t.hight / 2
}
func (t *Triangle) Set(b float64, h float64) {
t.bottom = b
t.hight = h
}
// ==== Triangle End ====
func main() {
rect := new(Rectangle)
rect.Set(2, 3)
areaIntf := Shaper(rect) //這種方法只能將指針類型的類示例賦值給接口
fmt.Printf("The rect has area: %f\n", areaIntf.Area())
triangle := new(Triangle)
triangle.Set(2, 3)
areaIntf = Shaper(triangle) //這種方法只能將指針類型的類示例賦值給接口
fmt.Printf("The triangle has area: %f\n", areaIntf.Area())
}