golang中的接口

x33g5p2x  于2021-11-12 转载在 Go  
字(2.3k)|赞(0)|评价(0)|浏览(521)

接口类型的定义与实现

接口是一种抽象类型,它仅包含一些方法签名,没有给出具体实现。

接口意味着约定。你拿到一个接口以后,你可以通过查看它包含的方法来知道它能做什么。

type Speaker interface {
	Speak() string
}

上面的代码定义了一个名为Speaker的接口,它包含一个方法。我们能从中看出来,Speak方法不需要提供参数就可以获取一个字符串,好像是要说点什么。

type Employee struct {
	ID            int
	Name, Address string
}

type EmployeeManager struct {
	Employee     // 匿名成员
	ManagerLevel int
}

func (e *Employee) Speak() string {
	return fmt.Sprintf("[%d, %s], from %s", e.ID, e.Name, e.Address)
}

func (e *EmployeeManager) Speak() string {
	return fmt.Sprintf("%s , level %d", e.Employee.Speak(), e.ManagerLevel)
}

上面的代码包含两个类型,这两个类型都实现了接口。实现接口的方式并不像有些语言一样需要明确的申明出来它implements了哪些接口,在golang中,只需要将接口中的所有方法全部实现了,就算实现了这个接口。

接口同样可以组合其它接口,就像struct中的匿名成员一样。

var employee = Employee{
	ID:      3,
	Name:    "fooEmployee",
	Address: "beijing",
}

var manager = EmployeeManager{
	Employee: Employee{
		ID:      2,
		Name:    "fooManager",
		Address: "beijing",
	},
	ManagerLevel: 4,
}

var speaker Speaker
// 如果写成 speaker = employee,则编译器会报错,因为方法的接收者类型是 *Employee 。
speaker = &employee		
PrintSpeak(speaker)

PrintSpeak(&manager)

在使用接口时,可以显式地定义一个Speaker类型的变量,这个变量的可赋值性调用方法更加严格。在方法调用时,一个结构体的变量可以调用以*T为接收者的方法,但在接口类型的赋值上,编译器是比较严格的。

万能类型 interface{}

interface{}这个类型,里面没有定义任何方法,也就意味着它没有做出任何承诺。看上去没啥用,其实它跟编译器的关系最好,可以赋值给它任意类型的变量。

var any interface{}

any = 1
any = "hello"
any = true
any = map[string]int{}

fmt.Println这个函数的参数就是可变长度的interface{},也就意味着它的参数相传什么传什么,想传几个传几个。

func Println(a ...interface{}) (n int, err error)

接口值

可以把接口类型想象成一个结构体,它包含两部分,动态类型和动态值。所以,如果一个接口被绑定了一个类型的话,即便这个类型的变量值是nil,但这个接口就不是nil了。

var any interface{}
fmt.Println(any == nil)					// true

var nilMap map[string]int = nil
any = nilMap

fmt.Println(nilMap == nil)				// true
fmt.Println(any == nil)					// false!  此时的any,已经具备了明确的动态类型,即便动态类型的变量是nil

接口断言

那么,对于上面的问题,如何判断接口里面的值到底是个什么呢?答案就是使用接口断言。简单来讲,接口断言就是通过一个断言类型来求接口中对应的值,看下面的代码

if realMap, ok := any.(map[string]int); ok {
	fmt.Println(realMap == nil)
}

接口断言的语法是var.(T),如果var中包含T,则将T返回,ok为true。如果T是固定类型,那么第一个返回值就是它的值,而如果T还是一个接口类型,那么返回的仍然是一个接口值。

到目前为止,我们可以将一个较大的类型赋值给一个较小的接口类型,还可以通过接口断言来从较小的接口类型中尝试地拿到较大的类型,这个闭环就产生了。从结果上看,接口断言就类似类型检测和类型转换。

类型分支

既然存在interface{}这样的万能类型,可以将任何东西都赋值给它,也可以通过接口断言取出到底赋值了个什么,但如果可能的类型很多的话,代码会比较冗余,golang对此专门提供了语法支持,这种语法就叫类型分支。

switch value := any.(type) {
case nil:
	fmt.Println("value is nil")
case string:
	fmt.Printf("%/s", value)
case map[string]int:
	fmt.Println(value)
}

需要注意的是,case nil是当any这个变量为nil,另外,这个神奇的value在不同的case语句块中的类型也是动态的,比如在case string中,value就是字符串类型,在case map[string]int中,value就是一个字典类型。

相关文章