Go语言接口用法详解(附带实例)
简而言之,接口是一组方法的集合,Go 语言中使用关键字 type interface 定义。
接口的语法结构如下:
接口虽然是一种类型,但它并没有描述具体的值是什么,也不会告诉你它的基础类型是什么,以及数据是如何存储的。接口仅描述这个值能做什么,有什么方法。
比如对于一个叫作“可以写字的工具”的接口来说,凡是满足“写字”这个方法的事物都可实现这个接口,无论是钢笔、铅笔、记号笔还是笔记本电脑。
注意,接口的定义非常简单,但要注意,接口由使用者定义!这是 Go 语言与其他面向对象的语言不同的地方。
鸭子类型是动态类型的一种风格,是一种对象推断策略,它关注的是“对象的行为”,而非对象类型本身。
Go 语言用接口来支持鸭子类型。Go 语言的鸭子类型既具有 Python 和 C++ 中鸭子类型的灵活性,又有 Java 中类型检查的安全性。在 Go 语言中,接口的实现是隐式的,判断类型 T 是否为实现接口 I 的依据,就是检查 T 是否实现了接口 I 声明的所有方法。
我们来看一个示例。首先定义两种结构体 Foodie 和 Child。不同人眼中的鸭子是不一样的,对于美食家来说,烤鸭就是他眼中的鸭子;而对于小朋友来说,数字 2 就是鸭子。没人在乎鸭子是什么颜色,长着圆脑袋还是方脑袋。
在二者之间有了关联或者协议(interface)后,他们就可以登上共同的舞台,说说自己眼中的鸭子是什么样的了。示例代码如下:
从代码的角度来看,只要是实现了接口 type IntroduceAboutDuck interface 中 WhatIsADuck 方法的类型(可以是结构体,也可以是基本类型的别名),都可以认为遵循了协议(接口)IntroduceAboutDuck,它们就可以在这一件事上有相同的行为,只是表现的方式和结果不一样罢了。
注意,接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。在前面的示例中,声明了接口 IntroduceAboutDuck 的变量 who,变量 who 又调用了接口中定义的方法 WhatIsADuck()。在实际调用时,会根据变量 who 包裹的类型去调用对应的 WhatIsADuck() 方法。
所谓鸭子类型模式,关注的仅仅是行为,只要实现了接口定义的方法(行为),那么就可以认为它是一只“鸭子”。在 Go 语言中,任何类型(可以是结构体,也可以是基本类型的别名)只要拥有一个接口需要的所有方法,那么它就实现了这个接口,不需要额外的显式声明。另外,不仅仅是结构体类型可以实现接口,拥有别名的类型同样也可以实现接口。
这里通过一个类比来说明,我们每个人都有多种行为,如果将其中特定的行为组合在一起,并从这些具体行为中抽象出共同特性,那么这类共同特性就可以称为协议。如下图所示:

图 1 接口与协议
只要“他”拥有某个协议中定义的所有行为,就可以认为“他”属于这个群体。可以看到,人与人之间通过协议有了关联,协议仅仅是将具有特定行为的人做了一个耦合。
接口(协议)有约束的功能。例如,“PaaS平台”这个协议发布了两个条件(Kubernetes 和容器云运行时),只有同时满足这两个条件的事物,才可以被认为是一个 PaaS 平台。在下图中,华为容器云、腾讯容器云、阿里容器云、亚马逊容器云均满足约束条件。

图 2 接口的约束
为了让结构体的方法更规范,可以使用接口对方法进行约束。在面向接口编程时,这种方式可以起到规范的效果。
这里用驾驶各种车来举例。在下面的示例中,car 和 bike 两个结构体代表两种不同类型的车,基于它们各自的 drive() 方法可知,它们都可以做出 drive 的动作(行为)。
为了改善这种情况,我们引入了接口。接口是一组方法的集合,它只定义不具体实现,实现的细节由与对象绑定的方法决定。我们加入如下代码:
我们加入 How2Drive() 函数,并修改 main() 函数的调用方式:
接口的语法结构如下:
type 接口名 interface{ 方法1() 方法2() ... 方法n() }接口有以下特性:
- 接口是抽象的,是对功能的约定,它会告诉使用者这个接口是用来做什么的;
- 接口是一系列方法的集合,其中包含若干个方法的声明;
- 接口中的方法都是抽象方法,不包含代码,也不允许包含变量;
- 接口是一种数据类型,默认是一个指针类型(引用类型),因此其零值就是 nil。
接口虽然是一种类型,但它并没有描述具体的值是什么,也不会告诉你它的基础类型是什么,以及数据是如何存储的。接口仅描述这个值能做什么,有什么方法。
比如对于一个叫作“可以写字的工具”的接口来说,凡是满足“写字”这个方法的事物都可实现这个接口,无论是钢笔、铅笔、记号笔还是笔记本电脑。
注意,接口的定义非常简单,但要注意,接口由使用者定义!这是 Go 语言与其他面向对象的语言不同的地方。
Go语言接口支持鸭子类型
在提到接口时,可能有读者听说过 Go语言中关于鸭子类型(duck typing)的设计。那么什么是鸭子类型,它与接口有什么关系呢?鸭子类型是动态类型的一种风格,是一种对象推断策略,它关注的是“对象的行为”,而非对象类型本身。
Go 语言用接口来支持鸭子类型。Go 语言的鸭子类型既具有 Python 和 C++ 中鸭子类型的灵活性,又有 Java 中类型检查的安全性。在 Go 语言中,接口的实现是隐式的,判断类型 T 是否为实现接口 I 的依据,就是检查 T 是否实现了接口 I 声明的所有方法。
我们来看一个示例。首先定义两种结构体 Foodie 和 Child。不同人眼中的鸭子是不一样的,对于美食家来说,烤鸭就是他眼中的鸭子;而对于小朋友来说,数字 2 就是鸭子。没人在乎鸭子是什么颜色,长着圆脑袋还是方脑袋。
// 定义 美食家 struct type Foodie struct{} //定义 小朋友 struct type Child struct {}接着,请美食家和小朋友分别使用 WhatIsADuck 这个方法来表达自己眼中的鸭子长什么样。请注意,WhatIsADuck() 方法是需求者(在此为这两种结构体)自己的行为。示例代码如下:
func (c *Foodie) WhatIsADuck() string { return "美食家眼中的鸭子是香喷喷的烤鸭" } func (c *Child) WhatIsADuck()string { return "小朋友眼中的鸭子是门前大桥下的24678" }接下来,我们想让美食家和小朋友在同一个舞台说出自己的想法。此时就轮到接口上场了,它提供了一种连接二者的方式。示例代码如下:
//定义了一个介绍鸭子的接口,连接介绍什么是鸭子的事物 //它的行为有:什么是鸭子 type IntroduceAboutDuck interface { WhatIsADuck() string }接口 IntroduceAboutDuck 对结构体 Foodie 和 Child 做了耦合,让两者有了共同的话题,即表达“自己眼中的鸭子是什么样的”。值得一提的是,此耦合是一个松耦合,有没有这个接口都不会对两个组件造成任何的影响。
在二者之间有了关联或者协议(interface)后,他们就可以登上共同的舞台,说说自己眼中的鸭子是什么样的了。示例代码如下:
var who IntroduceAboutDuck //共同的舞台(接口) who = new(Foodie) //组件1 fmt.Println(who.WhatIsADuck()) //输出:美食家眼中的鸭子是香喷喷的烤鸭 who = new(Child)//组件2 fmt.Println(who.WhatIsADuck()) //输出:小朋友眼中的鸭子是门前大桥下的24678就这样,在接口(笔者认为也可以把接口理解为协议)的连接下,两个不同的结构体 Foodie 和 Child 使用同一个方法 WhatIsADuck 说出了自己眼中鸭子的模样。
从代码的角度来看,只要是实现了接口 type IntroduceAboutDuck interface 中 WhatIsADuck 方法的类型(可以是结构体,也可以是基本类型的别名),都可以认为遵循了协议(接口)IntroduceAboutDuck,它们就可以在这一件事上有相同的行为,只是表现的方式和结果不一样罢了。
注意,接口本身不能创建实例,但是可以指向一个实现了该接口的自定义类型的变量。在前面的示例中,声明了接口 IntroduceAboutDuck 的变量 who,变量 who 又调用了接口中定义的方法 WhatIsADuck()。在实际调用时,会根据变量 who 包裹的类型去调用对应的 WhatIsADuck() 方法。
所谓鸭子类型模式,关注的仅仅是行为,只要实现了接口定义的方法(行为),那么就可以认为它是一只“鸭子”。在 Go 语言中,任何类型(可以是结构体,也可以是基本类型的别名)只要拥有一个接口需要的所有方法,那么它就实现了这个接口,不需要额外的显式声明。另外,不仅仅是结构体类型可以实现接口,拥有别名的类型同样也可以实现接口。
Go语言接口与协议
在 Go 语言中,把接口看作协议应该更容易理解。这里通过一个类比来说明,我们每个人都有多种行为,如果将其中特定的行为组合在一起,并从这些具体行为中抽象出共同特性,那么这类共同特性就可以称为协议。如下图所示:

图 1 接口与协议
只要“他”拥有某个协议中定义的所有行为,就可以认为“他”属于这个群体。可以看到,人与人之间通过协议有了关联,协议仅仅是将具有特定行为的人做了一个耦合。
接口(协议)有约束的功能。例如,“PaaS平台”这个协议发布了两个条件(Kubernetes 和容器云运行时),只有同时满足这两个条件的事物,才可以被认为是一个 PaaS 平台。在下图中,华为容器云、腾讯容器云、阿里容器云、亚马逊容器云均满足约束条件。

图 2 接口的约束
为了让结构体的方法更规范,可以使用接口对方法进行约束。在面向接口编程时,这种方式可以起到规范的效果。
Go语言接口实现多态
Go 语言中的多态是通过接口实现的。我们来看看用接口实现多态的过程。这里用驾驶各种车来举例。在下面的示例中,car 和 bike 两个结构体代表两种不同类型的车,基于它们各自的 drive() 方法可知,它们都可以做出 drive 的动作(行为)。
type car struct {} type bike struct { } func (c car) drive() { fmt.Println("汽车用四个轮子") } func (b bike) drive() { fmt.Println("自行车用两个轮子") } func main() { carInstance := car{} carInstance.drive() bikeInstance := bike{} bikeInstance.drive() }从上述代码中可以看到,先在 main() 函数中对这两个对象进行了实例化(初始化),然后执行了两次“对象.方法”操作。如果增加更多的类型,那么每种类型都需要执行上述步骤。
为了改善这种情况,我们引入了接口。接口是一组方法的集合,它只定义不具体实现,实现的细节由与对象绑定的方法决定。我们加入如下代码:
type driver interface { drive() }因为这些类型都有相同的行为 drive,且其具体的实现又与绑定的对象有关,所以这里将之抽象出来,形成接口 driver。
我们加入 How2Drive() 函数,并修改 main() 函数的调用方式:
func How2Drive(what driver) { what.drive() } func main() { carInstance := car{} bikeInstance := bike{} How2Drive(carInstance) How2Drive(bikeInstance) }在 Go 语言中,想要实现一个接口,只需要实现这个接口所定义的所有方法即可。在上述代码中,结构体 car 和 bike 都拥有签名相同的方法 drive,可见这两个结构体都实现了接口 driver。在调用 How2Drive() 函数时,传入不同的实例(carInstance 或者 bikeInstance),执行同一个动作(drive),完成的是不同的具体操作,这也就表明实现了多态。