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 ~