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 ~