5

golang中的接口使用

 2 years ago
source link: https://www.yangyanxing.com/article/interface-in-golang.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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 , 如果某个时刻你想知道这个接口变量到底是哪个结构体时,可以使用接口变量类型判断

  1. 使用 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

  1. 使用 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,所以上面的代码输出为

<nil> 333
{wangwang} 444
  1. 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.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK