9.接口
9.接口
Go语言提供了一种称为接口(interface)的数据类型,用于表示一组行为规范,即定义一组未实现的函数声明。谁调用接口谁负责参照接口的方法负责实现它们。
在Go语言中,使用组合实现对象特性的描述。对象内部使用结构体嵌套组合对象应该具有的特性,对外则通过暴露接口,让其他对象进行访问。Go语言中的接口设计是非侵入式的,接口编写者无需知道接口被哪些类型实现。而接口实现者只需要知道实现的是什么样子的接口,但也无需指明实现哪些接口。编译器知道最终编译时使用哪个类型实现哪个接口,或接口应该由谁来实现。接口是约束谁应该具有什么功能,实现某接口的方法,就具有该接口的功能。
在Go语言中,使用接口,通常遵循以下规则:
- 接口命名习惯在接口名称后面添加er做为后缀
- 参数列表、返回值列表可以不写
- 如果要想在包外使用接口,接口名称首字母需要大写
- 接口方法若想在包外使用,首字母也需要大写
- 接口中的方法应该设计合理,不要太多
9.1 接口定义和实现
接口是双方约定的一种合作协议,是一种类型,也是一种抽象结构。而接口实现指某一个结构体实现了接口声明的所有方法。而且一个结构体可以实现多个接口。接口定义的语法如下所示:
type name interface {
method1(parameters) returnValue
method2(parameters) returnValue
}
- name: 接口名称
- methodX:接口方法名称
- parameters: 接口方法参数列表
- returnValue: 返回值
接口方法只需要定义方法名称、参数名称和数据类型、返回的数据类型,无需在接口中编写方法的具体实现,方法的最终实现则实现者负责实现,一般是由结构体方法负责实现。示例代码如下所示:
package main
import "fmt"
// 定义接口
type Actions interface {
// 无参,无返回值
Walk()
// 无参,有返回值
Run() string
// 有参,无返回值
Shout(content string)
// 有参,有返回值
Rest(sleepTime int) string
}
// 定义结构体
type Dog struct {
name string
}
// 实现接口
func (d *Dog) Walk() {
fmt.Printf("%s is walking\n", d.name)
}
func (d *Dog) Run() string {
return fmt.Sprintf("%s is running %f km", d.name, 10.08)
}
func (d *Dog) Shout(content string) {
fmt.Printf("%s is shouting %s\n", d.name, content)
}
func (d *Dog) Rest(sleepTime int) string {
return fmt.Sprintf("%s is reset %d minustes", d.name, sleepTime)
}
func main() {
d := Dog{name: "小强"}
d.Walk()
fmt.Println(d.Rest(5))
// 这里需要注意,只有d已经全部实现a的所有接口,才能这样赋值
var a Actions = &d
a.Walk()
fmt.Println(a.Rest(5))
}
运行结果如下所示:
小强 is walking
小强 is reset 5 minustes
小强 is walking
小强 is reset 5 minustes
9.2 接口嵌套
结构体可以使用嵌套,接口同样也可以,通过接口嵌套,可以组成新的接口,并能形成简单的继承关系。示例如下所示:
- 第一种方式:最常见的接口嵌套
type Reader interface {
Read(p []byte) (n int,err error)
}
type Closer interface{
Close() error
}
type ReadCloser interface{
Reader
Closer
}
ReadCloser接口由Reader和Closer接口组合而成,也同时拥有Read和Close方法
- 第二种方式:在不同接口中定义相同的方法
接口嵌套允许在不同接口定义相同的方法,但程序会将两个同名方法视为同一个方法,因此方法的参数和返回值必须一致。即不同接口中的同名方法参数和返回值必须一致,否则程序执行会提示错误duplicate method Run (see details)。
type Run interface {
Run()
}
type Shout interface {
Shout(content string) string
}
type Actions interface {
Run
Shout
// 这样定义会报错
// Run(name string) string
Run()
}
接口嵌套通过多个接口组成一个新接口,使代码设计变得更加灵活,但为了降低各接口之间的方法命名冲突,各个接口和方法名称应尽量操持不同
9.3 空接口
空接口实际上是指空接口类型,写成interface{}或any。在空接口中,没有声明任何方法。因此,任何类型都无需要显式实现空接口的方法,因为任何数据类型都满足这个空接口的要求,因此,任何类型的值都可以看做是空接口类型。即空接口可以保存任意数据,也可以从空接口取出任意数据
为了使用方便,Go语言还为interface{}定义了一个别名any,即 type any = interface{}
示例代码如下所示:
- 使用空接口保存字符串、整型、布尔等基础数据
package main
import "fmt"
type EmptyInterface interface{}
func main() {
// 定义空接口方式一:
var a interface{} = "Surpass"
fmt.Printf("a value:%+v type is %[1]T\n", a)
a = 1.78
fmt.Printf("a value:%+v type is %[1]T\n", a)
// 定义空接口方式二:
var b EmptyInterface = "Surmount"
fmt.Printf("b value:%+v type is %[1]T\n", b)
b = true
fmt.Printf("b value:%+v type is %[1]T\n", b)
b = []int{1, 2, 3}
fmt.Printf("b value:%+v type is %[1]T\n", b)
}
运行结果如下所示:
a value:Surpass type is string
a value:1.78 type is float64
b value:Surmount type is string
b value:true type is bool
b value:[1 2 3] type is []int
- 切片、Map和结构体使用空接口
这里使用空接口主要是指切片、Map的元素和结构体的字段可以为任意数据。
package main
import (
"fmt"
)
func main() {
// 切片元素为空接口类型
s := []interface{}{"Surpass", "Shanghai", 1.89, false}
for _, v := range s {
fmt.Printf("slice v value is:%v,type is %[1]T\n", v)
}
m := map[string]interface{}{
"name": "Surpass",
"location": "Shanghai",
"height": 1.89,
}
for _, v := range m {
fmt.Printf("map v value is:%v,type is %[1]T\n", v)
}
ss := struct {
Name interface{}
Location interface{}
Height interface{}
Gender interface{}
}{
Name: "Surpass",
Location: "Shanghai",
Height: 1.89,
Gender: 1,
}
fmt.Printf("name type:%T Location type: %T Height type: %T Gender type:%T\n", ss.Name, ss.Location, ss.Height, ss.Gender)
}
运行结果如下所示:
slice v value is:Surpass,type is string
slice v value is:Shanghai,type is string
slice v value is:1.89,type is float64
slice v value is:false,type is bool
map v value is:Surpass,type is string
map v value is:Shanghai,type is string
map v value is:1.89,type is float64
name type:string Location type: string Height type: float64 Gender type:int
- 空接口作为函数的参数和返回值
package main
import "fmt"
func EmptyInterfaceFunc(data any) any {
fmt.Printf("parameter valeu: %v, type is %[1]T\n", data)
return data
}
func main() {
a := []interface{}{"Surpass", "Shanghai", 1.89, false}
EmptyInterfaceFunc(a)
EmptyInterfaceFunc(123)
EmptyInterfaceFunc(map[string]any{"name": "Surpass", "age": 28})
EmptyInterfaceFunc("Surpass")
EmptyInterfaceFunc(false)
}
运行结果如下所示:
parameter valeu: [Surpass Shanghai 1.89 false], type is []interface {}
parameter valeu: 123, type is int
parameter valeu: map[age:28 name:Surpass], type is map[string]interface {}
parameter valeu: Surpass, type is string
parameter valeu: false, type is bool
9.4 接口类型断言
接口类型断言可以将接口转换为另外一种接口,也可以将接口转换为另外的类型。其语法格式如下所示:
t:=x.(T)
- x: 接口变量
- T: 转换的目标类型
- t: 转换后的变量
若断言失败,即x没有实现T的接口方法,则panic。若为这种形式t,ok:=x.(T),则通过ok判断是否为T类型接口。示例代码如下所示:
9.4.1 基础类型接口断言
package main
import "fmt"
func main() {
var a interface{} = "Surpass"
var b interface{} = 100
if tb, ok := b.(int); ok {
fmt.Printf("tb转换成功,值为%v", tb)
} else {
fmt.Printf("tb转换失败")
}
ta := a.(int) //转换失败,出现panic
fmt.Printf("ta的值为:%v", ta)
}
代码运行结果如下所示:
tb转换成功,值为100
panic: interface conversion: interface {} is string, not int
9.4.2 结构体接口断言
package main
import "fmt"
// 定义接口
type Animal interface {
Run() string
Shout(content string)
Reset(time int) string
}
// 定义结构体
type Dog struct {
Name string
}
type Cat struct {
Name string
}
func (d *Dog) Run() string {
return fmt.Sprintf("%s is running\n", d.Name)
}
func (d *Dog) Shout(content string) {
fmt.Printf("%s is shout %s\n", d.Name, content)
}
func (d *Dog) Reset(time int) string {
return fmt.Sprintf("%s is reset %d minustes\n", d.Name, time)
}
func (c *Cat) Run() string {
return fmt.Sprintf("%s is running\n", c.Name)
}
func main() {
var a Animal
d := &Dog{Name: "小强"}
a = d
if _, ok := a.(Animal); ok {
fmt.Printf("%#v转换成功", a)
} else {
fmt.Printf("%#v转换失败", a)
}
// 因为Cat未全部实现接口Animal的方法,以下代码会出现报错
// c := &Cat{Name: "小花"}
// a = c
// if _, ok := a.(Animal); ok {
// fmt.Printf("%#v转换成功", a)
// } else {
// fmt.Printf("%#v转换失败", a)
// }
}
代码运行结果如下所示:
&main.Dog{Name:"小强"}转换成功
9.4.3 switch断言
可以使用switch断言来对接口做多种类型的断言,基本语法如下所示:
switch i.(type){
case nil:
...
case string:
...
case int:
...
default:
...
}
// 或
switch v:=i.(type){
case nil:
...
case string:
...
case int:
...
default:
...
}
示例代码如下所示:
package main
import "fmt"
func main() {
var a interface{} = "Surpass"
switch a.(type) {
case string:
fmt.Printf("%v type is string\n", a)
case int:
fmt.Printf("%v type is int\n", a)
case bool:
fmt.Printf("%v type is bool\n", a)
case nil:
fmt.Printf("%v type is unknown\n", a)
}
// 在switch中使用转换后的结果
switch v := a.(type) {
case string:
fmt.Printf("string: %+v", v)
case int:
fmt.Printf("int: %+v", v)
case bool:
fmt.Printf("bool: %+v", v)
case nil:
fmt.Printf("nil: %+v", v)
}
}
运行结果如下所示:
Surpass type is string
string: Surpass
9.5 鸭子类型
在现实生活中,我们也许都见过真正的鸭子,它拥有生命、会游泳,但现实生活中也各类型鸭子模型或玩具,例如:大黄鸭。从人们认知的角度来看,它不是一只真正的鸭子,不仅没有生命,也不会游泳,所以也不具备现实鸭子的本能,但从鸭子类型角度来看,它就是一只鸭子。实际上,在编程语言的角度来看,只要走起来像鸭子,或在某一些方面像鸭子,那么它就是一只鸭子。即鸭子类型只关心事物的外部行为而不关心内部结构
在接口中,其方法必须要与结构体进行绑定;而每次使用时,都需要创建接口变量、创建结构体实例化变量、结构体与接口绑定等步骤,在使用上会造成很多不便。如果将接口与结构体的绑定的过程以函数实现,只要传入结构体实例化变量就能自动执行接口方法,是不是要方便很多。示例代码如下所示:
package main
import (
"fmt"
)
type Actions interface {
Shout(content string)
}
type Duck struct {
Name string
}
type Chicken struct {
Name string
}
type Bird struct {
Name string
}
func (d *Duck) Shout(content string) {
fmt.Printf("%s %s\n", d.Name, content)
}
func (c *Chicken) Shout(content string) {
fmt.Printf("%s %s\n", c.Name, content)
}
func (b *Bird) Shout(content string) {
fmt.Printf("%s %s\n", b.Name, content)
}
func Shout(a Actions, content string) {
a.Shout(content)
}
func main() {
d := &Duck{Name: "我是一只鸭子"}
c := &Chicken{Name: "我是一只小鸡"}
b := &Bird{Name: "我是一只小鸟"}
Shout(d, "嘎嘎")
Shout(c, "咯咯")
Shout(b, "啾啾")
}
代码运行结果如下所示:
我是一只鸭子 嘎嘎
我是一只小鸡 咯咯
我是一只小鸟 啾啾
本文同步在微信订阅号上发布,如各位小伙伴们喜欢我的文章,也可以关注我的微信订阅号:woaitest,或扫描下面的二维码添加关注:

作者: Surpassme
来源: http://www.jianshu.com/u/28161b7c9995/
http://www.cnblogs.com/surpassme/
声明:本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出 原文链接 ,否则保留追究法律责任的权利。如有问题,可发送邮件 联系。让我们尊重原创者版权,共同营造良好的IT朋友圈。

浙公网安备 33010602011771号