Go-接口

本文内容基于《Go语言核心编程》,李文塔著。


1 接口

1.1 基本概念

1.1.1 接口声明

1.1.2 接口初始化

1.1.3 接口方法调用

1.1.4 接口的动态类型和静态类型

1.2 接口运算

1.2.1 类型断言

1.2.2 类型查询

1.2.3 接口优点和使用形式


1 接口

1.1 基本概念

1.1.1 接口声明

Go语言的接口分为接口字面量类型和接口命名类型,接口的声明使用interface关键字,接口命名类型使用type关键字声明:

// 接口字面量类型
interface {
    MethodSignature1
    MethodSignature2
}

// 接口命名类型
type INterfaceName interface {
    MethodSignature1
    MethodSignature2
}

接口定义大括号内可以是方法声明的集合,也可以嵌入另一个接口类型匿名字段,还可以是二者的混合:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

// 以下声明等价
type ReadWriter interface {
    Read
    Writer
}

type ReadWriter interface {
    Read
    Write(p []byte) (n int, err error)
}

type ReadWriter interface {
    Read(p []byte) (n int, err error)
    Write(p []byte) (n int, err error)
}

区别方法声明和函数声明:

// 方法声明 = 方法名 + 方法签名
MethodName(InputTypeList) OutputTypeList

// 函数声明 = 函数名 + 函数签名
func FunctionName(InputTypeList) OutputTypeList

接口的特点:

  • 接口的命名一般以“er”结尾;
  • 接口定义的内部方法声明不需要func引导;
  • 在接口定义中,只有方法声明没有方法实现。

1.1.2 接口初始化

接口起到抽象和适配的作用,接口绑定具体类型的实例的过程称为接口初始化,支持两种直接初始化方法:

  • 实例赋值接口:如果具体类型实例的方法集是某个接口的方法集的超集,则称该具体实例实现了接口,可以将该具体类型的实例直接赋值给接口类型的变量,此时编译器会进行静态的类型检查,接口被初始化后,调用接口的方法就相当于调用接口绑定的具体类型的方法,这就是接口调用的语意;
  • 接口变量赋值接口变量:已经初始化的接口类型变量a直接赋值给另一种接口变量b,要求b的方法集是a的方法集的子集,此时Go编译器会在编译时进行方法集静态检查,这个过程也是接口初始化的一种方式,此时接口变量b绑定的具体实例是接口变量a绑定的具体实例的副本。
type Aer interface {
	Method1()
	Method2()
}

type Ber interface {
	Method1()
}

func main() {
	var a Aer
	var b Ber
	b = a
	//a = b	// Cannot use 'b' (type Ber) as type Aer Type does not implement 'Aer' as some methods are missing: Method2() 
}

1.1.3 接口方法调用

接口方法的调用的最终地址是在运行期决定的,将具体类型变量赋值给接口后,会使用具体类型的方法指针初始化接口变量,当调用接口变量的方法时,实际上是间接地调用实例的方法,接口方法调用不是一种直接的调用,有一定的运行时开销(直接调用未初始化的接口变量的方法会产生panic):

type Printer interface {
	Print()
}

type S struct {}

func (s S) Print() {
	fmt.Println("something")
}

func main() {
	var i Printer
	//i.Print()		// Method call 'i.Print()' may lead to nil pointer dereference
	i = S{}
	i.Print()
}

1.1.4 接口的动态类型和静态类型

  • 动态类型:

接口绑定的具体实例的类型成为接口的动态类型,接口可以绑定不同类型的实例,所以接口的动态类型是随着绑定的不同类型实例而发生变化的。

  • 静态类型:

接口被定义时,其类型就已经被确定,这个类型叫接口的静态类型,接口的静态类型在定义时就被确定,静态类型的本质特征就是接口的方法签名集合,两个接口如果方法签名集合相同(顺序可以不同),则这两个接口在语义上完全等价,它们之间不需要强制类型转换就可以相互赋值。

1.2 接口运算

1.2.1 类型断言

接口类型断言的语法形式如下,有两层语义:

  • 如果TypeName是一个具体类型名,则类型断言用于判断接口变量i绑定的实例类型是否就是具体类型TypeName;
  • 如果TypeName是一个接口类型名,则类型断言用于判断接口变量i绑定的实例类型是否同时实现了TypeName接口。
i.(TypeName)

接口类型断言的两种语法表现:

  • 直接赋值模式:
type Aer interface {
	Method1()
	Method2()
}

type S1 struct {}

func (s S1) Method1() {}

func (s S1) Method2() {}

type S2 struct {}

func main() {
	s1 := S1{}
	var i interface{} = s1
	o1 := i.(S1)
	o1.Method1()
	o1.Method2()

	o2 := i.(S2)		// panic: interface conversion: interface {} is main.S1, not main.S2
	fmt.Println(o2)
}
  • common, ok表达模式:
type Aer interface {
	Method1()
	Method2()
}

type S1 struct {}

func (s S1) Method1() {}

func (s S1) Method2() {}

type S2 struct {}

func main() {
	s1 := S1{}
	var i interface{} = s1
	o1, ok := i.(S1)
	if ok {
		o1.Method1()
		o1.Method2()
	}

	o2, ok := i.(S2)
	if ok {
		fmt.Println(o2)
	}
}

1.2.2 类型查询

接口类型查询的语法格式如下,有两层语义:

  • 查询一个接口变量底层绑定的底层变量的具体类型是什么;
  • 查询一个接口变量底层绑定的底层变量是否还实现了其他接口。
switch v := i.(type)
case type1:
    xxx
case type2:
    xxx
default:
    xxx

1.2.3 接口优点和使用形式

接口优点:

  • 解耦:复杂系统进行垂直和水平的分割是常用的设计手段,在层与层之间使用接口进行抽象和解耦是一种好的编程策略,Go的非侵入式的接口使层与层之间的代码更加干净,具体类型和实现的接口之间不需要显式声明,增加了接口使用的自由度;
  • 实现泛型:由于现阶段Go语言还不支持泛型,使用空接口作为函数或方法参数能够用在需要泛型的场景中。

接口使用形式:

  • 作为结构内嵌字段;
  • 作为函数或方法的形参;
  • 作为函数或方法的返回值;
  • 作为其他接口定义的嵌入字段。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值