1. 程式人生 > 實用技巧 >11.Go語言-介面

11.Go語言-介面

2.介面

  • 介面定義了一個物件的行為規範。

2.1介面

2.1.1介面型別

  • Go語言中介面是一種型別,一種抽象型別。
  • interface是一組methods的集合。

2.1.2為什麼要用介面

package main

import "fmt"

type Cat struct{}

func (c Cat) Say() string {return "miao miao miao"}

type Dog struct{}

func (d Dog) Say() string {return "wang wang wang"}

func main() {
	c := Cat{}
	fmt.Println(c)
	fmt.Println(c.Say())
	d := Dog{}
	fmt.Println(d)
	fmt.Println(d.Say())
}


{}
miao miao miao
{}
wang wang wang
  • 上述程式碼你會發現有很多重複的程式碼。。

2.1.3介面定義

介面是一個或多個方法簽名的集合。
    任何型別的方法集中只要擁有該介面'對應的全部方法'簽名。
    就表示它 "實現" 了該介面,無須在該型別上顯式宣告實現了哪個介面。
    這稱為Structural Typing。
    所謂對應方法,是指有相同名稱、引數列表 (不包括引數名) 以及返回值。
    當然,該型別還可以有其他方法。

    介面只有方法宣告,沒有實現,沒有資料欄位。
    介面可以匿名嵌入其他介面,或嵌入到結構中。
    物件賦值給介面時,會發生拷貝,而介面內部儲存的是指向這個複製品的指標,既無法修改複製品的狀態,也無法獲取指標。
    只有當介面儲存的型別和物件都為nil時,接口才等於nil。
    介面呼叫不會做receiver的自動轉換。
    介面同樣支援匿名欄位方法。
    介面也可實現類似OOP中的多型。
    空介面可以作為任何型別資料的容器。
    一個型別可實現多個介面。
    介面命名習慣以 er 結尾。
  • 介面定義格式:
type 介面型別名 interface{
        方法名1( 引數列表1 ) 返回值列表1
        方法名2( 引數列表2 ) 返回值列表2
        …
    }
  • 其中
1.介面名:使用type將介面定義為自定義的型別名。Go語言的介面在命名時,一般會在單詞後面新增er,如有寫操作的介面叫Writer,有字串功能的介面叫Stringer等。介面名最好要能突出該介面的型別含義。
    2.方法名:當方法名首字母是大寫且這個介面型別名首字母也是大寫時,這個方法可以被介面所在的包(package)之外的程式碼訪問。
    3.引數列表、返回值列表:引數列表和返回值列表中的引數變數名可以省略。

2.1.4

  • 定義一個介面:
package main

import "fmt"

type Sayer interface {
	say()
}
type dog struct {}
type cat struct {}

func (d dog) say() {
	fmt.Println("wang wang wang")
}

func (c cat) say() {
	fmt.Println("miao miao miao")
}
func main() {
	d:=dog{}
	d.say()
	c:=cat{}
	c.say()
}

wang wang wang
miao miao miao

2.1.5介面型別變數

  • 介面型別變數能夠儲存所有實現該介面例項。
package main

import "fmt"

type Sayer interface {
	say()
}
type dog struct {}
type cat struct {}

func (d dog) say() {
	fmt.Println("wang wang wang")
}

func (c cat) say() {
	fmt.Println("miao miao miao")
}
func main() {
	var x Sayer
	d:=dog{}
	c:=cat{}
	x = d
	x.say()
	x = c
	x.say()
}

wang wang wang
miao miao miao

2.1.6值接受者和指標接受者實現介面的區別

  • 使用值接受者實現介面和使用指標接受者實現介面有什麼區別?
  • 值接受者和指標接受者實現介面:
package main

import "fmt"

type dog struct {}

type Mover interface {
	move()
}


func (d dog) move() {
	fmt.Println("狗會動了")
}


func main() {
	var x Mover
	var wangci = dog{}//wangci是dog型別
	x = wangci//用x接受dog型別
	var fugui = &dog{}//fugui是*dog型別
	x = fugui// x可以接收*dog型別
	x.move()
}
  • 從上面的程式碼中我們可以發現,使用值接收者實現介面之後,不管是dog結構體還是結構體指標*dog型別的變數都可以賦值給該介面變數。因為Go語言中有對指標型別變數求值的語法糖,dog指標fugui內部會自動求值*fugui

2.1.7一個示例

  • 下面程式碼會不會抱錯
package main

import "fmt"

type People interface {
	Speak(string) string
}

type Student struct {}



func (stu *Student) 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))
}
  • 會報錯,應該改為&Student{}

2.1.8一個型別實現多個介面

package main

import "fmt"

type Sayer interface {
	say()
}


type Mover interface {
	move()
}

type dog struct {
	name string
}

func (d dog) say() {
	fmt.Printf("%s 會叫汪汪汪\n", d.name)
}

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()
}

旺財 會叫汪汪汪
旺財 會動啊

2.1.9多個型別實現同一介面

  • Go語言中不同型別還可以實現同一介面
package main

import "fmt"

type Mover interface {
	move()
}

type dog struct {
	name string
}

type car struct {
	brand string
}

func (d dog) move() {
	fmt.Printf("%s會跑\n", d.name)
}
func (c car) move() {
	fmt.Printf("%s 速度是70邁\n", c.brand)
}

func main() {
	var x Mover
	var a = dog{name:"wangcai"}
	var b = car{brand: "benteng"}
	x = a
	x.move()
	x = b
	x.move()
}

wangcai會跑
benteng 速度是70邁

  • 一個介面方法,不一定由一個型別完全實現,介面的方法可以通過在型別中嵌入其他型別或者結構體來實現。
package main

import "fmt"

type Mover interface {
	move()
}

type dog struct {
	name string
}

type car struct {
	brand string
}

func (d dog) move() {
	fmt.Printf("%s會跑\n", d.name)
}
func (c car) move() {
	fmt.Printf("%s 速度是70邁\n", c.brand)
}

type WashingMachine interface {
	wash()
	dry()
}

type dryer struct {}


func (d dryer) dry() {
	fmt.Println("甩一下")
}

type haier struct {
	dryer
}

func (h haier) wash() {
	fmt.Println("洗刷帥")
}


func main() {
	var x Mover
	var a = dog{name:"wangcai"}
	var b = car{brand: "benteng"}
	x = a
	x.move()
	x = b
	x.move()
}
  • 一個介面方法,不一定需要由一個型別完全實現
package main

import "fmt"

type WashingMachine interface {
	wash()
	dry()
}
type dryer struct {}
func (d dryer) dry() {
	fmt.Println("甩一下")
}
type haier struct {
	dryer
}
func (h haier) wash() {
	fmt.Println("洗刷帥")
}
func main() {
	var x WashingMachine
	var hai = haier{}
	x = hai
	x.wash()
	x.dry()
}
洗刷帥
甩一下

2.1.10介面巢狀

  • 介面與介面間可以通過潛逃創造出新的介面

    package main
    
    import "fmt"
    
    type Sayer interface {
       say()
    }
    
    type Mover interface {
       move()
    }
    
    type animal interface {
       Sayer
       Mover
    }
    
    type cat struct {
       name string
    }
    func (c cat) say() {
       fmt.Println("miao miao miao", c.name)
    }
    func (c cat) move() {
       fmt.Println("mao is move")
    }
    
    func main() {
       var x animal
       x = cat{name:"huahua"}
       x.move()
       x.say()
    }
    

2.1.11空介面

  • 空介面定義:空介面是沒有定義任何方法的介面,因此任何型別都實現了空介面。空介面型別的變數可以儲存任意型別的變數。
package main

import "fmt"

func main() {
	var x interface{}
	s := "ppro.cn"
	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)
}

type:string value:ppro.cn
type:int, value:100
type:bool, value:true

2.1.12空介面應用

  • 空介面作為函式的引數。
func show(a interface{}){
	fmt.Printf("type:%T  value:%v\n",a,a)
}
  • 空介面作為map的值
func main() {
	var studentInfo = make(map[string]interface{})
	studentInfo["name"] = "libai"
	studentInfo["age"] = 18
	studentInfo["married"] = false
	fmt.Println(studentInfo)
}
map[age:18 married:false name:libai]
// 鍵是string,值是interface

2.1.13型別斷言

  • 空介面可以儲存任意型別的值,那我們如何獲取儲存的具體資料?

  • 介面值:

    一個介面的值(簡稱介面值) 是由一個具體型別和具體型別的值兩部分組成的,這兩部分分別稱為介面的動態型別動態值。

  • 如果想要判斷空介面中值可以使用型別斷言,語法是:

    x.(T)
    
  • 其中:

    x: 表示型別為interface{}的變數
    T:表示斷言x可能是的型別
    
  • 該語法返回兩個引數,第一個引數是x轉化為T型別後的變數,第二個值是一個布林值,若為true則表示斷言成功,為false則表示斷言失敗。

    package main
    
    import (
    	"fmt"
    )
    
    func main() {
    	var x interface{}
    	v, ok := x.(string)
    	if ok {
    		fmt.Println(v)
    	} else {
    		fmt.Println("型別斷言失敗")
    	}
    }
    
  • Switch...case語句

    func justifyType(x interface{}){
    	switch v := x.(type) {
    	case string:
    		fmt.Printf("x is string,value is %v\n", v)
    	case int:
    		fmt.Printf("x is a int is %v\n",v)
    	case bool:
    		fmt.Printf("x is bool is %v\n",v)
    	default:
    		fmt.Printf("unsupport type!")
    	}
    }
    
  • 因為空介面可以儲存任意型別值的特點,所以空介面在Go語言中的使用十分廣泛。

    關於介面需要注意的是,只有當有兩個或兩個以上的具體型別必須以相同的方式進行處理時才需要定義介面。不要為了介面而寫介面,那樣只會增加不必要的抽象,導致不必要的執行時損耗。

  • 原文連結:http://www.topgoer.com/