Golang介面型別-下篇

語言: CN / TW / HK

本文是Golang介面型別-上篇的續篇內容

1、介面嵌入

和結構體 struct 一樣,介面之中也可以嵌入已存在的介面,從而實現介面的擴充套件

1.1 定義

// Sender 定義Sender介面
type Sender interface {
	Send(msg string) error
}

// Receiver 定義Receiver介面
type Receiver interface {
	Receive() (string, error)
}

// Client Client,由Sender和Receiver組合
type Client interface {
	Sender  // 匿名嵌入
	Receiver  // 匿名嵌入
	Open() error
	Close() error
}

1.2 實現

// MSNClient 定義MSNClient結構體,並實現Client介面中Open/Send/Receive/Close方法
type MSNClient struct{
}

func (c MSNClient) Open() error {
	fmt.Println("Open")
	return nil
}

func (c MSNClient) Close() error {
	fmt.Println("Close")
	return nil
}

func (c MSNClient) Send(msg string) error {
	fmt.Println("send:", msg)
	return nil
}

func (c MSNClient) Receive() (string, error) {
	fmt.Println("Receive")
	return "", nil
}

1.3 使用

func main() {
	//msn := MSNClient{}
	//var s Sender = msn
	//var r Receiver = msn
	//var c Client = msn
	//s.Send("1")
	//r.Receive()
	//c.Open()
	//defer c.Close()
	//c.Send("2")
	//c.Receive()
	var client Client = MSNClient{}
	client.Open()
	client.Send("Hi")
	msg,_ := client.Receive()
	fmt.Printf("%q\n", msg)
	client.Close()
}

2、匿名介面和空介面

2.1 匿名介面

在定義變數時將型別指定為介面的函式簽名的介面,此時叫匿名介面,匿名介面常用於初始化一次介面變數的場景

//通過匿名介面宣告介面變數
var closer interface {
	Close() error
}
closer = msn
closer.Close()

2.2 空介面

不包含任何函式簽名的介面叫做空介面,空介面宣告的變數可以賦值為任何型別的變數(任意介面)

  • 空介面型別用 interface{} 表示,注意有 {}
  • 空介面沒有定義任何方法,因此任意型別都實現了空介面
  • func square(x interface{}){} 該函式可以接收任意資料型別
  • slice 的元素、 mapkeyvalue 都可以是空介面型別

定義語法: interface{}

package main

import "fmt"

type EStruct struct {
}

type Empty interface {
}

func main() {
	es := EStruct{}
	var e interface{} = 1
	fmt.Println(es, e)  // {} 1
	e = "test"
	fmt.Println(e)  // test
	e = true
	fmt.Println(e)  // true
	e = es
	fmt.Println(e)  // {}
}

2.3 使用場景

宣告函式引數型別為 interface{} ,用於接收任意型別的變數

package main

import "fmt"

type EStruct struct{
}

func printType(args ...interface{}) {
	fmt.Println("------------------------")
	for _, arg := range args {
		//fmt.Println(arg)
		switch v := arg.(type) {
		case int:
			fmt.Printf("Int: %T %v\n", v, v)
		case string:
			fmt.Printf("String: %T %v\n", v, v)
		default:
			fmt.Printf("Other: %T %v\n", v, v)
		}
	}
}

func main() {
	es := EStruct{}
	printType(1, "test", true, es)
	/*
	Int: int 1
	String: string test
	Other: bool true
	Other: main.EStruct {}
	 */
}

3、介面斷言和查詢

型別賦值成了介面型別,能否通過某種方式轉換成當時賦值的型別呢?

當父集介面或者型別物件賦值給介面變數後,需要將介面變數重新轉換為原來的型別,需要使用型別斷言/查詢

3.1 斷言

語法: 介面變數.(Type)
判斷一個介面能否轉換成具體型別

// 使用型別斷言資訊轉換
sender01, ok := ssender.(Sender)
fmt.Printf("%T, %#v, %v\n", sender01, sender01, ok)  // *main.WechatSender, &main.WechatSender{ID:""}, true
sender01.SendAll([]string{"張三", "李四"},"你好")
if sender02, ok := ssender.(*WechatSender); ok {
	fmt.Printf("%T, %#v, %v\n", sender02, sender02, ok)  // *main.WechatSender, &main.WechatSender{ID:""}, true
	fmt.Println(sender02.ID)
}
if sender03, ok := ssender.(*EmailSender); !ok {
	fmt.Printf("%T, %#v, %v\n", sender03, sender03, false)  // *main.EmailSender, (*main.EmailSender)(nil), false
}

3.2 查詢

可以通過 switch-case + 介面變數.(type) 查詢變數型別,並選擇對應的分支塊

// 使用型別查詢
sender = &EmailSender{"test"}
switch v := sender.(type) {
case EmailSender:
	fmt.Println("EmailSender", v.SmtpAddr)
case *EmailSender:
	fmt.Println("*EmailSender", v.SmtpAddr)  // *EmailSender test
case *SmsSender:
	fmt.Println("*SmsSender", v.SmsAPI)
case *WechatSender:
	fmt.Println("*WechatSender", v.ID)
default:
	fmt.Printf("error, %#v\n", v)
}

利用斷言判斷資料型別

package main

import "fmt"

func assert(i interface{})  {
	switch v := i.(type) {
	case int:  // v已被轉為int型別
		//v := i.(int)
		fmt.Printf("%d\n", v)
		// 在 Type Switch語句的case子句中不能使用fallthrough
	case float64:  // v已被轉為float64型別
		fmt.Printf("%f\n", v)
	case byte, uint16, string:  // 如果case後面跟多種type,則v還是interface{}型別
		fmt.Printf("%T %v\n", i, i)
	}
}

func main()  {
	var i interface{}
	var a int
	var b float64
	var c byte
	i = a
	assert(i)  // 0
	i = b
	assert(i)  // 0.000000
	i = c
	assert(i)  // uint8 0
}

See you ~