返回介绍

上卷 程序设计

中卷 标准库

下卷 运行时

源码剖析

附录

7. 接口

发布于 2024-10-12 19:15:48 字数 6160 浏览 0 评论 0 收藏 0

接口(interface)是多个方法声明集合,代表一种调用契约。

只要目标类型方法集包含接口声明的全部方法,就视为实现该接口,无需显示声明。当然,单个目标类型可实现多个接口。在设计上,接口解除了显式类型依赖(DIP,依赖倒置),提供面向对象多态性。应该定义小型、灵活及组合性接口(ISP,接口隔离),减少可视方法,屏蔽对象内部结构和实现细节。

  • 不能有字段。
  • 只能声明方法,不能实现。
  • 可嵌入其他接口。
  • 通常以 er 作为名称后缀。
  • 空接口( interface{} , any )没有任何方法声明。

接口实现的依据是方法集,所以要区分 T.set*T.set

type Xer interface {
	test()
	toString() string
}

// -------------------------

type N struct{}
func (*N) test() {}
func (N) toString() string { return "" }

// -------------------------

func main() {
	var n N

	// var t Xer = n
	//             ~ N does not implement Xer 
    //               test method has pointer receiver

	var t Xer = &n
    
	t.test()
	t.toString()
}

匿名接口可直接用于变量定义,或作为结构字段类型。

type N struct{}
func (N) test() {}

// -------------------------

type node struct {
	value interface {
		test()
	}
}

// -------------------------

func main() {
	var t interface {
		test()
	} = N{}
    
	n := node{ value: t }
	n.value.test()
}

空接口可被赋值任何对象。

type any = interface{}     // 别名
func main() {
	var i any = 123
	fmt.Println(i)

	i = "abc"
	fmt.Println(i)
}

注意,接口会复制目标对象,通常以指针替代原始值。

type Xer interface {
	toString() string
}

// -------------------------

type N struct {
	x int
}

func (n N) toString() string { 
	return strconv.Itoa(n.x)
}

// -------------------------

func main() {
	n := N{ 100 }
	var x Xer = n   // copy

	n.x = 200

	println(n.toString())  // 200
	println(x.toString())  // 100
}

匿名嵌入

像匿名字段那样,嵌入其他接口。目标类型方法集中,必须包含嵌入接口在内的全部方法实现。

  • 相当于导入(include)声明,非继承。
  • 不能嵌入自身或循环嵌入。
  • 不允许声明重载(overload)。
  • 允许签名相同的声明(并集去重)。
  • 鼓励小接口嵌入组合。

签名包括方法名、参数列表(数量、类型、排列顺序)和返回值,不包括参数名。

type Aer interface {
	Test()
}

type Ber interface {
	ToString(string) string
}

// -------------------------

type Xer interface {
	Aer
	Ber                            // 嵌入接口有相同声明。

	ToString(s string) string      // 签名相同(并集去重)。

	// ToString() string           // 不允许重载(签名不同)。
	// ~~~~~~ duplicate method

	Print()
}

// -------------------------

type N struct{}
func (N) ToString(string) string { return "" }
func (N) Test() {}
func (N) Print() {}

// -------------------------

func main() {
	i := Xer(N{})
	t := reflect.TypeOf(i)

	for i := 0; i < t.NumMethod(); i++ {
		fmt.Println(t.Method(i))
	}
}

/*

   Print:  func(main.N)
    Test:  func(main.N)
ToString:  func(main.N, string) string

*/

类型转换

超集接口(即便非嵌入)可隐式转换为子集,反之不行。

  • 超集包含子集的全部声明(含嵌入)。
  • 和声明顺序无关。
type Aer interface {
	toString(string) string
}

// -------------------------

type Xer interface {
	// Aer

	test()
	toString(string) string
}

// -------------------------

type N struct{}
func (*N) test() {}
func (N) toString(s string) string { return s }

// -------------------------

func main() {
	var x Xer = &N{}     // super
	var a Aer = x        // sub

	a.toString("abc")

	// var x2 Xer = a
	//              ~ Aer does not implement Xer (missing test method)
    
	// x2 := Xer(a)
	//           ~ Aer does not implement Xer    
}

类型推断将接口还原为原始类型,或判断是否实现了某个更具体的接口类型。

func main() {
	var x Xer = &N{}     // super
	var a Aer = x        // sub

    // 原始类型。
	n, ok := a.(*N)
	fmt.Println(n, ok)   // true

    // 接口。
	x2, ok := a.(Xer)
	fmt.Println(x2, ok)  // true
}
func main() {
    var e any = (*int)(nil) // !!!
	_, ok := e.(*int)

	println(ok)  // true
}

还可用 switch 语句在多种类型间做出推断匹配,如此空接口就有更多发挥空间。

  • 未使用变量视为错误。
  • 不支持 fallthrough
func main() {
	var i any = &N{}

	switch v := i.(type) {
	case nil: 
	case *int:
	case func()string:
	case *N: fmt.Println(v)
	case Xer: fmt.Println(v)
	default:
	}
}
func main() {
	var i any = &N{}

	switch v := i.(type) {   // v declared but not used
	case *N: fallthrough     // fallthrough statement out of place
	default:
	}
}

接口比较

如实现接口的动态类型支持,可做相等( ==!= )运算。

func main() {
    println(any(nil) == any(nil))
    println(any(100) == any(100))

    // println(any(map[string]int{}) == any(map[string]int{}))
    //             ^ comparing uncomparable type map[string]int
}

内部实现 runtime.efaceeqruntime.ifaceeq 直接比较 .data

允许方式:

  • 接口类型相同。
  • 接口类型不同,但声明完全相同。
  • 接口类型是超集或子集。
type Aer interface {
	test()
}

type Ber interface {
	test()
}

type Cer interface {
	toString() string
}

type Xer interface {
	test()
	toString() string
}

// -------------------------

type N struct { x int }
func (N) test() {}
func (N) toString() string { return "" }

// -------------------------

func main() {
	a, b := N{ 100 }, N{ 100 }
	println(a == b)               // true

	// 相同类型接口。
	println(Aer(a)  == Aer(b))    // true
	println(Aer(&a) == Aer(&b))   // false

	// 不同接口类型,但声明相同(或超集)。
	println(Aer(a) == Ber(b))     // true
	println(Aer(a) == Xer(b))     // true

	// 不同接口类型,声明不同。
	// println(Aer(a) == Cer(b))
    //         mismatched types Aer and Cer

	// 对象和它实现的接口类型(b impl Aer)。
	println(Aer(a) == b)          // true
}

接口内部由两个字段组成: itabdata

只有两字段都为 nil 时,接口才等于 nil 。可利用反射完善判断结果。

不能直接以指针判断,因为编译器可能将其指向 runtime.zerobasezeroVal 全局变量。

func main() {
    var n *N
    var x Xer = n      // type != nil
    
    println(x == nil)  // false
    println(x == nil || reflect.ValueOf(x).IsNil()) // true
}
func main() {
	var t1 any                  // type == nil
	var t2 any = ([]int)(nil)   // type != nil

	println(t1 == nil)  // true
	println(t2 == nil)  // false

	println(t1 == nil || reflect.ValueOf(t1).IsNil())  // true
	println(t2 == nil || reflect.ValueOf(t2).IsNil())  // true
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文