go語言的介面

爬呀爬Xjm發表於2019-07-16

go語言的介面

簡介

  • 介面是雙方規定的一種合作協議,介面實現者不需要關心介面會被怎樣使用,呼叫者不需要關心介面的實現細節。介面是一種型別。也是一種抽象結構。不會暴露所含資料的格式、型別及結構。比如只要一臺洗衣機有洗衣服的功能和甩乾的功能,我們就稱為洗衣機,不關心屬性(資料),只關心行為(方法)。
  • 介面和其他動態語言的鴨子模型有密切關係。比如說pythonjavascript。任何型別,只要實現了該介面中的方法集,那麼就屬於這個型別。
  • 每個介面由數個方法組成。

介面的定義

格式:

type 介面型別名 interface {
    方法名1( 引數列表1 ) 返回值列表1
    方法名2( 引數列表2 ) 返回值列表2
    …
}
  • 介面型別名:使用type將介面定義為自定義的型別名
  • 方法名:當方法名首字母是大寫時,且這個介面型別名首字母也是大寫時,這個方法可以被介面所在的包之外的程式碼訪問。
  • 引數列表、返回值列表:引數列表和返回值列表的引數變數名可以被忽略

示例:

// 可以吃
type Eat interface {
    eat()
}

看到這個介面時,只知道他有一個eat()方法。並不知道誰實現了這些介面,也不知道是怎麼實現的這些這個方法的。

實現介面的條件

  • 介面的方法與實現介面的型別方法格式一致
  • 介面的方法與實現介面的型別方法數目保持一致。即介面中的所有方法均被實現。
package main

import (
    "fmt"
)

// 定義一個鴨子型別介面
type Duck interface {
    // 鴨子叫
    GaGa()
    // 鴨子走
    YouYong()
}

// 定義一個小雞型別的結構體
type Chicken struct {
}

// 為小雞結構體實現GaGa
func (c Chicken) GaGa() {
    fmt.Println("我是小雞,但我也會嘎嘎叫")
}

// 為小雞結構體實現YouYong
func (c Chicken) YouYong() {
    fmt.Println("我是小雞,但我也會游泳")
}

func main() {
    
    // 例項化一個小雞
    c := new(Chicken)

    // 初始化一個小雞小鴨合體型別
    var duckChicken Duck = c

    // 小雞小鴨開始嘎嘎
    duckChicken.GaGa()
    // 小雞小鴨開始游泳
    duckChicken.YouYong()

}

可以看出來,duckChicken可以直接呼叫GaGaYouYong。他並不知道這兩個方法內部怎麼實現的。

值型別接收者和指標型接收者實現介面

package main

import (
    "fmt"
)

// 定義一個汪汪叫的介面
type WangWager interface {
    Wang()
}

// 定義一個狗結構體
type Dog struct {
    name string
}

func (d Dog) Wang()  {
    fmt.Println("汪汪叫")
}

func main() {
    d1 := Dog{"小狗1"}
    d2 := &Dog{
        "小狗2",
    }
    var w1 WangWager = d1
    w1.Wang()

    var w2 WangWager = d2
    w2.Wang()

}

可以發現,使用值型別實現介面後,不管dog結構體例項化是指標型還是值型別。都可以賦值給該介面變數。因為go語言內部有對指標型別變數求值的語法糖。

但是指標型實現介面後,只能指標型變數賦值給介面變數。

型別與介面的關係

型別與介面有一對多和多對多的關係。

一(型別)對多(介面):

package main

import (
    "fmt"
)

// 定義一個Writer介面
type Writer interface {
    Writer(p []byte) (n int, err error)
}

// 定義一個Closer介面
type Closer interface {
    Closer() error
}

// 定義一個socket結構體型別
type Socket struct {
}

// 為socket實現一個Writer()方法
func (s *Socket) Writer(p []byte) (n int, err error) {
    fmt.Println("開始寫入")
    return n, err
}

// 為socket實現一個Closer()方法
func (s *Socket) Closer() (err error) {
    fmt.Println("開始關閉")
    return err
}

// 定義一個函式,負責寫
func useWriter(w Writer) {
    // 執行w介面的寫方法
    _, _ := w.Writer(nil)
}
// 定義一個函式,負責關閉
func useCloser(c Closer) {
    // 執行c介面的關閉方法
    _ := c.Closer()
}

func main() {
    fmt.Println("理解型別與介面的關係")

    // 型別和介面之間有一對多和多對一的關係。

    // 一個型別可以實現多個介面
    // 例項化socker結構體
    s := new(Socket)
    useWriter(s)
    useCloser(s)
}

可以看出兩個函式完全獨立,完全不知道對方的存在,也不知道使用自己的介面是socket使用的

多(型別)對一(介面)

介面的方法不一定要一個結構體型別完全實現,介面的方法可以通過結構體嵌入實現。

package main

import (
    "fmt"
)

// 定義一個服務介面,實現了服務開啟和日誌輸出的方法
type Service interface {
    Start()     // 啟動服務
    Log(string) // 日誌輸出
}

// 定義一個遊戲服務結構體
type GameService struct {
    Logger // 內嵌logger結構體
}

// 為遊戲結構體實現遊戲服務的啟動method
func (g *GameService) Start() {
    fmt.Println("遊戲服務啟動成功")
}

// 定義一個日誌器結構體
type Logger struct {
}

// 為日誌伺服器結構體實現日誌輸出的method
func (l *Logger) Log(s string) {
    fmt.Println(s)
}

func main() {

    // 多個型別可以實現相同的介面
    var ser Service = new(GameService)
    ser.Start()
    ser.Log("日誌輸出")
}

可以看出,服務介面下一個服務啟動功能和日誌輸出功能,但是遊戲服務型別並沒有實現日誌輸出功能,而是間接通過內嵌日誌型別來實現,日誌型別實現了日誌輸出功能,所有遊戲服務型別實現的介面可以直接使用日誌輸出功能。、

介面的巢狀組合

不僅型別與型別之間可以巢狀,介面與介面之間也可以巢狀。

package main

import (
    "fmt"
)

// 定義一個Say介面
type Say interface {
    Say(s string)
}

// 定義一個Run介面
type Run interface {
    Run(n int)
}

// 定義一個Skill介面
type Skill interface {
    // 巢狀了兩個介面
    Say
    Run
}

// 定義一個Person結構體
type Person struct {
}

// 為Person結構體實現Say方法
func (p *Person) Say(s string) {
    fmt.Println("說話:", s)
}

// 為Person結構體實現Run方法
func (p *Person) Run(n int) {
    fmt.Println("步數:", n)
}

func main() {
    // 實現人的結構體的技能型別
    var s Skill = new(Person)
    s.Say("Life Is Short Let's Go")
    s.Run(9999)

    // 實現啞巴(人)結構體的技能型別,無法說話,只能跑步
    var yaBa Run = new(Person)
    yaBa.Run(100000)

}

空介面

空介面是介面型別的特殊形式,空介面沒有任何方法。因此任何型別都無須實現,從實現的角度來看。任何值都滿足這個介面的需求。因此空介面型別可以儲存任何值,也可以從中取值。

儲存值

package main

import (
    "fmt"
)

func main() {
    // 將各種資料型別的值儲存到空介面
    var any interface{}

    any = 1
    fmt.Println(any)
    any = 0.99
    fmt.Println(any)
    any = "Hello Gp"
    fmt.Println(any)
    any = true
    fmt.Println(any)
    any = []string{}
    fmt.Println(any)
    any = [...]string{}
    fmt.Println(any)
    any = map[string]int{}
    fmt.Println(any)
}

空介面的應用

空介面作為函式的引數
package main

import "fmt"

func show(v interface{})  {
    fmt.Println(v)
}

func main() {
    show("江子牙")
    show(520)
    show(true)
}
空介面作為map的value
package main

import "fmt"

func main() {
    a := map[string]interface{}{
        "name":    "江子牙",
        "age":     21,
        "isLogin": true,
    }
    fmt.Println(a)
}

介面和型別之間的轉換

go 語言中使用介面斷言(type assertions) 將介面轉換成另一外一個介面,也可以將介面轉另外的型別。使用非常頻繁。

型別斷言

格式:

t := i.(T)
  • i:代表介面變數
  • T:代表轉換的目標型別
  • t:轉換後的變數

如果i沒有完全實現T介面的方法,這個語句會觸發當機,觸發當機不是很友好,因為有另一種保守寫法。

t, ok := i.(T)

這種寫法的好處就是如果傳送介面未實現時,將會返回一個布林值false,即ok的值,而且t的型別為0。正常實現時,oktrue

介面轉化為其他介面

例子:

package main

import (
    "fmt"
)

// 定義飛行動物介面
type Flyer interface {
    Fly(s string)
}

// 定義爬行動物介面
type Walker interface {
    Walk(s string)
}

// 定義小鳥類結構體
type Bird struct {
}

// 定義小豬類結構體
type Pig struct {
}

// 為小鳥類實現飛的技能和走路的技能
func (b *Bird) Fly(s string) {
    fmt.Println("小鳥飛行:", s)
}
func (b *Bird) Walk(s string) {
    fmt.Println("小鳥走路:", s)
}

// 為小豬實現走路的技能
func (p *Pig) Walk(s string) {
    fmt.Println("死豬跑不動嗎:", s)
}

func main() {
    // 先建立一個字典來存放介面的資訊。
    animals := map[string]interface{}{
        "bird": new(Bird),
        "pig":  new(Pig),
    }
    fmt.Println(animals)
    // 迴圈map
    for name, obj := range animals {
        // 型別斷言:判斷obj是爬行動物還是飛行動物
        f, isFly := obj.(Flyer)
        w, isWalk := obj.(Walker)
        fmt.Printf("name:%s\tisFlyer:%v\tisWalker:%v\n", name, isFly, isWalk)

        // 型別判斷
        if isFly {
            f.Fly("100米")
        }
        if isWalk {
            w.Walk("1000步")
        }
    }
}

介面轉化為型別

package main

import (
    "fmt"
)

// 定義爬行動物介面
type Walker interface {
    Walk(s string)
}

// 定義小豬類結構體
type Pig struct {
}

// 為小豬實現走路的技能
func (p *Pig) Walk(s string) {
    fmt.Println("死豬跑不動嗎:", s)
}

func main() {
    // 將介面轉為其他型別:可以實現將介面轉為普通的指標型別
    // 例項化出一個小豬結構體
    p1 := new(Pig)
    // 宣告一個型別為小豬爬行類w介面
    var w Walker = p1
    fmt.Println(w)
    fmt.Printf("%T\n", w)
    fmt.Printf("%T\n", p1)
    // 將w介面轉為*Pig型別
    p2 := w.(*Pig)
    fmt.Printf("p1 = %p\np2 = %p", p1, p2)
}

執行結果:

&{}
*main.Pig
*main.Pig
p1 = 0x5861b0
p2 = 0x5861b0

判斷介面中變數的型別

判斷基本型別

package main

import (
    "fmt"
)

func printType(i interface{}) {
    switch i.(type) {
    case int:
        fmt.Println("int型別")
    case string:
        fmt.Println("string型別")
    case bool:
        fmt.Println("bool型別")
    default:
        fmt.Println("不知名型別")
    }
}

func main() {
    // 使用型別分支判斷基本型別
    printType("str")
    printType(1)
    printType(true)
    printType([]string{})
}

執行結果:

string型別
int型別
bool型別
不知名型別

判斷介面型別

package main

import (
    "fmt"
)

// 刷臉的介面
type useFace interface {
    Face(string)
}

// 刷人民幣值為100的介面
type useOneHundred interface {
    OneHundred(string)
}

// 支付寶方式結構體
type Alipy struct {
}

// 現金方式方式結構體
type Cash struct {
}

// 為支付寶提供人臉支付的方法
func (a Alipy) Face(s string) {
    fmt.Println(s)
}

// 為現金支付提供支付100元的方法
func (c Cash) OneHundred(s string) {
    fmt.Println(s)
}

func printPay(payMethod interface{}) {
    var pay = payMethod
    // 判斷介面型別
    switch payMethod.(type) {
    case useFace:
        face := pay.(*Alipy)
        face.Face("刷臉")
    case useOneHundred:
        cash := pay.(*Cash)
        cash.OneHundred("支付100毛爺爺")
    }
}

func main() {

    //使用型別分支判斷介面型別
    printPay(new(Alipy))
    printPay(new(Cash))
}

執行結果:

刷臉
支付100毛爺爺

相關文章