《Go語言聖經》學習筆記:7.Go接口詳解

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
}

如果 vvarI 轉換到類型 T 的值,ok 會是 true;否則 v 是類型 T 的零值,okfalse

要是多個類型實現了同一個接口,比如前面的 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())
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章