Go語言基礎之接口

Go語言基礎之接口

接口(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等。接口名最好要能突出該接口的類型含義。
  • 方法名:當方法名首字母是大寫且這個接口類型名首字母也是大寫時,這個方法能夠被接口所在的包(package)以外的代碼訪問。
  • 參數列表、返回值列表:參數列表和返回值列表中的參數變量名能夠省略。

舉個例子:

type writer interface{
    Write([]byte) error
}

當你看到這個接口類型的值時,你不知道它是什麼,惟一知道的就是能夠經過它的Write方法來作一些事情。

實現接口的條件

一個對象只要所有實現了接口中的方法,那麼就實現了這個接口。換句話說,接口就是一個須要實現的方法列表

咱們來定義一個Sayer接口:

// Sayer 接口
type Sayer interface {
    say()
}

定義dogcat兩個結構體:

type dog struct {}

type cat struct {}

由於Sayer接口裏只有一個say方法,因此咱們只須要給dogcat分別實現say方法就能夠實現Sayer接口了。

// dog實現了Sayer接口
func (d dog) say() {
    fmt.Println("汪汪汪")
}

// cat實現了Sayer接口
func (c cat) say() {
    fmt.Println("喵喵喵")
}

接口的實現就是這麼簡單,只要實現了接口中的全部方法,就實現了這個接口。

接口類型變量

那實現了接口有什麼用呢?

接口類型變量可以存儲全部實現了該接口的實例。 例如上面的示例中,Sayer類型的變量可以存儲dogcat類型的變量。

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的值

使用空接口實現能夠保存任意值的字典。

// 空接口做爲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

undefined

想要判斷空接口中的值這個時候就可使用類型斷言,其語法格式:

x.(T)

其中:

  • x:表示類型爲interface{}的變量
  • T:表示斷言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語言中的使用十分普遍。

關於接口須要注意的是,只有當有兩個或兩個以上的具體類型必須以相同的方式進行處理時才須要定義接口。不要爲了接口而寫接口,那樣只會增長沒必要要的抽象,致使沒必要要的運行時損耗。

相關文章
相關標籤/搜索