golang中的接口使用

作者: adm 分类: go 发布时间: 2023-04-21

golang 中的接口和别的面向对象中的接口有很大的不同。

接口的定义

//定义一个接口,它有一个run 方法
type Runable interface {
	run()
}

定义一个Runable 接口,它有一个方法,run() , 这个方法没有参数也没有返回值

结构体可以定义一个方法,如果某个结构体定义了run() 方法,则说明该结构体实现了 Runable 接口,并不像java 或者 python 中的类,在定义的时候在显示的说明继承自哪个接口。

接口可以有值
这个和大多数的面向对象语言有区别,它们的接口是不可以实例化的,但是Go中的接口是可以”实例”的

func main() {
	var r Runable
	fmt.Println(r)
}

打印出来是nil, 也就是说,实例化接口得到的是一个空指针,空指针此时是不能直接调用接口方法的,如 r.run(),

之后如果想要将该指针赋值,也要指向一个实现了run() 方法的结构体变量

如果指向了实例,则可以调用run方法。

package main

import "fmt"

//定义一个接口,它有一个run 方法
type Runable interface {
	run()
}

type Person struct {
	name string
	age int
}

type Cat struct {
	name string
}

//实现了Runable接口的run方法
func (self *Person) run() {
	fmt.Println("Person run....")
}

func main() {
	var r Runable
	fmt.Println(r)
    // 这里会编译错误
    //r.run() 
	p := Person{"yyx", 18}
	c := Cat{"mimi"}
	r = &p
	//r = c
    r.run()  // 这里会调用Person的run方法
}

上面代码在 r = c 时会出现编译错误

Cannot use 'c' (type Cat) as the type Runable Type does not implement 'Runable' as some methods are missing: run()

意思就是 Cat 类并没有实现 Runable 接口的 run() 方法

通过接口实现多态
多态在面向对象语言中非常重要,由于有了多态,才会发挥面向对象的优势,我们在定义一个函数时,参数可以使用接口类型,在真正调用该函数的时候,只要传入一个实现了该接口的结构体即可。

package main

import "fmt"

//定义一个接口,它有一个run 方法
type Runable interface {
	run()
}

type Person struct {
	name string
	age int
}

type Dog struct {
	name string
}

func (self *Person) run() {
	fmt.Println("Person run....")
}

func (self Dog) run() {
	fmt.Println("Dog run....")
}

// 定义一个test函数,参数为Runable 接口类型
func test(obj Runable)  {
	obj.run()
}

func main() {
	p := Person{"yyx", 18}
	d := Dog{"wangwang"}
	// 将实现了Runable 接口的结构体用为参数,当调用相应的方法
	test(&p)
	test(d)

}

上面代码输出为

Person run....
Dog run....

上面代码中定义了一个test(obj Runable) 函数,参数为Runable 接口,在之后的调用中,只要传一个实现了Runable接口的结构体即可, test函数中调用了run() 方法,将会调用结构体相应的run方法。

结构体方法的接收者类型
结构体在实现接口方法的时候,接收者参数可以是值类型也可以是指针类型,这个是之后的调用过程中是有影响的

在上面的代码中

func (self *Person) run() {
	fmt.Println("Person run....")
}

func (self Dog) run() {
	fmt.Println("Dog run....")
}

Person 使用的是指针类型,Dog 使用的是值类型,

那么在之后的test() 函数调用时

p := Person{"yyx", 18}
d := Dog{"wangwang"}
// 将实现了Runable 接口的结构体用为参数,当调用相应的方法
test(&p)
test(d)
Person 类型的就要传地址,Dog 类型的就可以直接传值。

另外,对于接口值也需要注意

var r Runable
fmt.Println(r)
p := Person{"yyx", 18}
d := Dog{"wangwang"}
r = &p
r = d

r 在刚初始化的时候是nil, 如果实现方法接收者为指针类型,那么r也要指向结构体变量地址,如接收者为值类型,那么r 可以直接指向结构体变量。

接口变量的类型判断
如上面的代码定义,有一个Runable 接口,有两个结构体,Person 和 Dog , 如果某个时刻你想知道这个接口变量到底是哪个结构体时,可以使用接口变量类型判断

使用 v := varI.(T) 形式

func main() {
	var r Runable
	p := Person{"yyx", 18}
	d := Dog{"wangwang"}
	r = d
	dd := r.(Dog)
	fmt.Println(dd, "ddd")
	pp := r.(*Person)
	fmt.Println(pp, "pp")
}

如果接口变量是T类型的话,则返回的v就是接口所指向的变量,如果不是T类型的话,则发生panic

上面的结果为

{wangwang} ddd
panic: interface conversion: main.Runable is main.Dog, not *main.Person

使用 if v, ok := varI.(T); ok 形式
上面的代码会导致panic, 更加安全的方式上使用if 判断的形式

func main() {
	var r Runable
	p := Person{"yyx", 18}
	d := Dog{"wangwang"}
	r = d
	if person, ok := r.(*Person); ok{
		fmt.Println(person, 222)
	}else {
		fmt.Println(person, 333)
	}
	if dog, ok := r.(Dog); ok{
		fmt.Println(dog, 444)
	}else{
		fmt.Println(dog, 555)
	}
}

由于将r指向了Dog的变量d,所以上面的代码输出为

 333
{wangwang} 444
type-switch 形式
func main() {
	var r Runable
	fmt.Println(r)
	p := Person{"yyx", 18}
	d := Dog{"wangwang"}
	r = d
	switch t:= r.(type) {
	case *Person:
		fmt.Printf("Type Person %T value %v \n", t,t)
	case Dog:
		fmt.Printf("Type Dog %T value %v \n", t,t)
	case nil:
		fmt.Println("t is nil....")
	default:
		fmt.Println("default case......")
	}
}

case 中除了nil 和default 之外的结构体都需要实现接口方法,否则编译错误。

判断值是否实现了某个接口
如果想要判断某个值是否实现了某个接口, 可以使用v.(Inter)

func main() {
	var r Runable
	fmt.Println(r)
	p := Person{"yyx", 18}
	d := Dog{"wangwang"}
	// 判断某个值是否实现了Runable接口
	r = &p
	if _, ok := r.(Runable); ok{
		fmt.Println("p 实现了Runable接口")
	}else{
		fmt.Println("p 没有实现Runable接口")
	}
}

我的理解,这里的检查某个接口值是否实现了某个接口,相当于判断了该接口是否是nil, 当接口值想要指向某个变量时,如果那个变量没有实现接口方法,也不可能指向成功。

上面的代码,如果将 r = &p 注释掉,则OK 为false,此时只能说明nil 刚初始化,指向的是nil.

如果觉得我的文章对您有用,请随意赞赏。您的支持将鼓励我继续创作!