面向对象世界中的接口的一般定义是“接口定义对象的行为”。它表示让指定对象应该做什么。实现这种行为的方法(实现细节)是针对对象的。
在Go中,接口是一组方法签名。当类型为接口中的所有方法提供定义时,它被称为实现接口。它与OOP非常相似。接口指定了类型应该具有的方法,类型决定了如何实现这些方法。
在go语言中,接口和类型的实现关系,是非侵入式的。
一、接口的定义和使用
1、interface的定义
type interfaceName interface {
methodName(parameter list) (return list)
methodName(parameter list) (return list)
...
methodName(parameter list) (return list)
}
其中:
-
interfaceName
接口名:使用type将接口定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,如有写操作的接口叫Writer,有字符串功能的接口叫Stringer等。接口名最好要能突出该接口的类型含义。 -
methodName
方法名:当方法名首字母是大写且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。 -
parameter list
参数列表、return list
返回值列表:参数列表和返回值列表中的参数变量名可以省略。
package main
import "fmt"
// Person 接口
type Preson interface {
Say()
}
// Student 结构体
type Student struct {
Name string
Age int
Address string
}
// Say Student的方法
func (s Student) Say() {
fmt.Println(s.Name, "说:hello!")
}
func main() {
s := Student{"tom", 20, "苏州市"}
s.Say()
}
运行结果
tom 说:hello!
Student实现了接口Person中的所有方法,那么就可以说Student实现了Person接口。
2、接口类型
接口类型变量能够存储所有实现了该接口的实例。
- 当需要接口类型的对象时,可以使用任意实现类对象代替
- 接口对象不能访问实现类中的属性
package main
import "fmt"
// USB 接口
type USB interface {
start()
end()
}
//Mouse 鼠标结构体
type Mouse struct {
name string
}
// FlashDisk U盘结构体
type FlashDisk struct {
name string
}
// start Mouse的方法
func (m Mouse) start() {
fmt.Println(m.name, "鼠标开始工作了。。。")
}
// end Mouse的方法
func (m Mouse) end() {
fmt.Println(m.name, "鼠标结束工作了。。。")
}
// start FlashDisk的方法
func (f FlashDisk) start() {
fmt.Println(f.name, "鼠标开始工作了。。。")
}
// end FlashDisk的方法
func (f FlashDisk) end() {
fmt.Println(f.name, "鼠标结束工作了。。。")
}
// test USB测试函数
func test(u USB) {
u.start()
u.end()
}
func main() {
m := Mouse{"罗技"}
f := FlashDisk{"金士顿"}
test(m)
test(f)
fmt.Println("---------------------------")
// 定义一个USB接口类型的变量u
var u USB
// m实现了USB接口的所有方法,m就实现了USB接口,就可以把m赋值给u。
u = m
u.start()
u.end()
// f也实现了USB接口的所有方法,f也实现了USB接口,也可以把f赋值给u。
u = f
u.start()
u.end()
}
运行结果
罗技 鼠标开始工作了。。。
罗技 鼠标结束工作了。。。
金士顿 鼠标开始工作了。。。
金士顿 鼠标结束工作了。。。
---------------------------
罗技 鼠标开始工作了。。。
罗技 鼠标结束工作了。。。
金士顿 鼠标开始工作了。。。
金士顿 鼠标结束工作了。。。
3、值接收者和指针接收者实现接口的区别
- 值接收者实现接口
package main
import "fmt"
// Mover 接口
type Mover interface {
move()
}
// Dog 结构体
type Dog struct {
}
// move Dog的方法
func (d Dog) move() {
fmt.Println("dog move")
}
func main() {
var m Mover
wangcai := Dog{} // 旺财是Dog类型
m = wangcai // m可以接受Dog类型
m.move()
fugui := &Dog{} // 富贵是*Dog类型
m = fugui // m可以接受*Dog类型
m.move()
}
从上面的代码中我们可以发现,使用值接收者实现接口之后,不管是Dog
结构体还是结构体指针*Dog
类型的变量都可以赋值给该接口变量。因为Go语言中有对指针类型变量求值的语法糖,Dog
指针fugui
内部会自动求值*fugui
。
- 指针接受者实现接口
package main
import "fmt"
// Mover 接口
type Mover interface {
move()
}
// Dog 结构体
type Dog struct {
}
// move Dog的方法
func (d *Dog) move() {
fmt.Println("dog move")
}
func main() {
var m Mover
wangcai := Dog{} // 旺财是Dog类型
// m = wangcai // m不可以接受Dog类型
wangcai.move()
fugui := &Dog{} // 富贵是*Dog类型
m = fugui // m可以接受*Dog类型
m.move()
}
此时实现Mover接口的是*Dog
类型,所以不能给m
传入Dog
类型的wangcai
,此时m
只能存储*Dog
类型的值。
4、结构嵌套
- 一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。
- 多个不同的类型还可以实现同一接口
- 接口与接口间可以通过嵌套创造出新的接口。
package main
import "fmt"
// Mover 接口
type Mover interface {
move()
}
// Sayer 接口
type Sayer interface {
say()
}
// Dog 结构体
type Dog struct {
}
// move Dog的方法
func (d Dog) move() {
fmt.Println("dog move")
}
// say Dog的方法
func (d Dog) say() {
fmt.Println("dog say")
}
// Cat 结构体
type Cat struct {
}
// move Cat的方法
func (c Cat) move() {
fmt.Println("cat move")
}
// say Cat的方法
func (c Cat) say() {
fmt.Println("cat say")
}
// Animal 接口
type Animal interface {
Mover
Sayer
}
func main() {
d := Dog{}
c := Cat{}
var a Animal
a = d
a.say()
a.move()
a = c
a.say()
a.move()
}
运行结果
dog say
dog move
cat say
cat move
二、空接口
1、空接口的定义
- 空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。
- 空接口类型的变量可以存储任意类型的变量。
var variableName interface{}
2、空接口的应用
- 空接口作为函数的参数
使用空接口实现可以接收任意类型的函数参数。
package main
import "fmt"
func show(a interface{}) {
fmt.Println(a)
}
func main() {
a := 10
show(a)
b := "hello world"
show(b)
}
运行结果
10
hello world
- 空接口作为map的key和value
使用空接口实现可以保存任意值的字典。
package main
import "fmt"
func main() {
m := make(map[interface{}]interface{}, 10)
m = map[interface{}]interface{}{
10: "hello",
true: 21,
"id": 23.1,
}
fmt.Printf("%+v\n", m)
}
运行结果
map[true:21 10:hello id:23.1]
- 空接口作为切片slice或数组arry的元素
使用空接口实现可以在arry和slice中保存不同类型的数据。
package main
import "fmt"
func main() {
s := make([]interface{}, 10)
s = []interface{}{12, true, "hello", 23.123}
fmt.Printf("%+v\n", s)
a := [...]interface{}{31.23, "hello world", 12, false}
fmt.Printf("%+v\n", a)
}
运行结果
[12 true hello 23.123]
[31.23 hello world 12 false]
3、类型断言
空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?
想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:
// 安全类型断言
<目标类型的值>,<布尔参数> := <表达式>.( 目标类型 )
//非安全类型断言
<目标类型的值> := <表达式>.( 目标类型 )
package main
import "fmt"
func main() {
var a interface{}
a = true
v, ok := a.(bool)
if ok {
fmt.Println(v)
} else {
fmt.Println("断言失败")
}
}
运行结果
true
断言其实还有另一种形式,就是用在利用 switch语句判断接口的类型。每一个case会被顺序地考虑。当命中一个case 时,就会执行 case 中的语句,因此 case 语句的顺序是很重要的,因为很有可能会有多个 case匹配的情况。
package main
import "fmt"
func main() {
var a interface{}
a = "hello world"
justifyType(a)
}
func justifyType(a interface{}) {
switch v := a.(type) {
case string:
fmt.Println("a is string,", v)
case int:
fmt.Println("a is int,", v)
case bool:
fmt.Println("a is bool,", v)
default:
fmt.Println("unknow")
}
}
运行结果
a is string, hello world
关于接口需要注意的是,只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样只会增加不必要的抽象,导致不必要的运行时损耗。