Golang - interface

介绍

  • 重要结论
    • 由指针接受者实现的方法, 在赋值给接口时, 必须使用结构体指针
    • 由值接收者实现的方法, 在赋值给接口时, 可以是结构体, 也可以结构体指针
    • 接口的使用方式有两种: interfaceName(&obj).func(arg)interfaceInstance.func(arg)
  • interface 是一种类型, 抽象的类型, 区别于具体的类型
  • 实现: 一个 struct 只要实现了 interface 中的全部 func, 就是实现了这个 interface
  • Go 语言提倡面向 interface 编程
  • 支持 interface 嵌套
  • 特点
    • 优点: 非侵入式设计, 写起来更自由, 无需显式实现. 只要实现了与 interface 所包含的所有函数签名相同的方法即可
    • 缺点: duck-typing 风格并不关注 interface 的规则和含义, 也没法检查, 不能确定 struct 实现了哪些 interface, 只能通过 goru 工具查看

使用

基本用法

package main

import "fmt"

type Sender interface {
	send(string)
}

// 模拟指针接收者
type Phone struct {
	Num string
}

func (p *Phone) send(msg string) {
	fmt.Printf("from %v send %v\n", p.Num, msg)
}

// 模拟值接收者
type Mail struct {
	Addr string
}

func (m Mail) send(msg string) {
	fmt.Printf("from %v send %v\n", m.Addr, msg)
}

func main() {

	// 准备四个实例
	p1 := &Phone{Num: "111"}
	p2 := Phone{Num: "222"}  // 由于 phone 是指针接受者实现的 send(), 所以用法应该同 p1
	m1 := Mail{Addr: "aaa"}
	m2 := &Mail{Addr: "bbb"}

	// 方式 1: InterfaceInstance.func(arg)
	var iSender Sender
	iSender = p1
	iSender.send("xxx")
	iSender = m1
	iSender.send("xxx")
	iSender = m2
	iSender.send("xxx")

	// 方式 2: InterfaceName(obj/&obj).func(arg)
	Sender(p1).send("yyy")
	Sender(m1).send("yyy")
	Sender(m2).send("yyy")

	// 遍历使用, 元素为接口类型(实现 interface 的结构体/结构体指针)
	a := []Sender{p1, &p2, m1, m2}
	for i := 0; i < len(a); i++ {
		a[i].send("zzz")
	}
}

类型断言

package main

import "fmt"

func main() {

	// 准备工作
	x := &[]int{1, 2, 3}
	var i interface{}
	i = x

	// if 断言, 将 iSender 断言为 Phone 的指针
	// ok 为真则表示断言成功, v 为断言后的类型和值
	// ok 为假则表示断言失败, v 为对应类型的空值
	v, ok := i.(int)
	if ok {
		fmt.Printf("断言成功, %T, %v", v, v)
	} else {
        fmt.Printf("断言失败, %T, %v", v, v)
    }

	// switch 断言
	switch v := i.(type) {
	case int:
		// 根据 int 类型处理
		// do sth.
	case string:
		// 根据 string 类型处理
		// do sth.
	default:
		// 默认提示
	}
}

补充知识

空接口

  • 没有实现 任何func 的接口, 就是空接口
  • 任何类型都实现了空接口, 空接口可以接收任意类型的值
package main

import "fmt"

func main() {
	// 创建一个空接口
	var i interface{}

	i = 10
	fmt.Printf("%T, %[1]v, %T\n", i, &i) // int, 10, *interface {}

	i = "10"
	fmt.Printf("%T, %[1]v, %T\n", i, &i) // string, 10, *interface {}

	i = [1]int{10}
	fmt.Printf("%T, %[1]v, %T\n", i, &i) // [1]int, [10], *interface {}

	i = []int{10}
	fmt.Printf("%T, %[1]v, %T\n", i, &i) // []int, [10], *interface {}

	i = t{}
	fmt.Printf("%T, %[1]v, %T\n", i, &i) // main.t, {}, *interface {}
}

结构体方法实现接口的区别

  • 实现方法尽量使用指针作为接收者
  • structFunc值接收者时, interface 能使用指针和值
  • structFunc指针接收者时, interface 只能使用指针
  • 以上两种情况都存在时, 只能使用指针

底层实现

  • golanginterface 在底层由两种 struct 实现, ifaceeface
  • eface 就是 empty interface, 即"空接口"
    // 由于 Go 参数传递规则为值传递, 如果希望可以通过 interface 对实例数据修改, 则需要传入指针
    // 此时 data 指向的是实例指针地址, 从而对实例数据进行修改
    
    type eface struct {
        _type *_type           // 类型
        data  unsafe.Pointer   // 数据
    }
    
  • iface 就是 non-empty interface, 即"非空接口"
    type iface struct {
        tab  *itab             // 
        data unsafe.Pointer    // 数据, 和 iface 一样
    }
    
  • tab
    • interface type包含了一些关于interface本身的信息,比如package path,包含的method。上面提到的iface和eface是数据类型转换成interface之后的实体struct结构,而这里的interfacetype是定义interface的一种抽象表示。
    • type表示具体化的类型,与eface的 type类型相同。
    • hash字段其实是对_type.hash的拷贝,它会在interface的实例化时,用于快速判断目标类型和接口中的类型是否一致。另,Go的interface的Duck-typing机制也是依赖这个字段来实现。
    • fun字段其实是一个动态大小的数组,虽然声明时是固定大小为1,但在使用时会直接通过fun指针获取其中的数据,并且不会检查数组的边界,所以该数组中保存的元素数量是不确定的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值