在最简单的形式下,接口类型就是一组方法签名。
下面是一个接口定义的例子,它包含两个方法 Add 和 Value:
type Counter interface {
Add(increment int)
Value() int
}
像 increment 这样的形参名可以从接口定义中省略,但它们通常能提高可读性。
Go 中的接口名不包含 Interface 或 I 这个词。
相反,它们通常以 er 结尾,例如 Reader、Stringer。
任何定义了接口方法的类型,都会自动隐式地“实现”该接口。
Go 中没有 implements 关键字。
下面的类型实现了我们上面看到的 Counter 接口。
type Stats struct {
value int
// ...
}
func (s Stats) Add(v int) {
s.value += v
}
func (s Stats) Value() int {
return s.value
}
func (s Stats) SomeOtherMethod() {
// The type can have additional methods not mentioned in the interface.
}
对于实现接口来说,方法使用的是值接收者还是指针接收者都无所谓。 (如果你对这些内容不确定,可以回顾方法的概念。)
接口类型的值可以保存任何实现了这些方法的值。[^1]
这意味着现在凡是需要 Counter 接口的地方,都可以使用 Stats。
func SetUpAnalytics(counter Counter) {
// ...
}
stats := Stats{}
SetUpAnalytics(stats)
// works because Stats implements Counter
由于接口是隐式实现的,一个类型可以轻松实现多个接口。 它只需要定义好所有必需的方法。
Go 中有一种非常特殊的接口类型,即包含零个方法的空接口类型。
空接口这样写:interface{}。
any 是 interface{} 的别名,而且更常用。
由于空接口没有方法,所以每个类型都隐式地实现了它。 这对于定义一个能泛化地接受任何值的函数很有帮助。 在这种情况下,函数形参就使用空接口类型。
柏林的新机场为他们的机器人实验室招聘了开发者,你就要去那里开始工作了。 他们有一些笨重、外表有点像人的机器人,正试着用它们来改善客户服务。
你上班后的第一个任务是编写一个程序,让机器人在人们在自助值机柜台扫描护照后,能用他们的母语向人们问好。
机器人对自己的本领很自豪,所以它总是先说自己会说哪种语言,然后再向对方问好。 例如,如果有人扫描了一本德国护照,机器人会说:
I can speak German: Hallo Dietrich!
你不会亲自为各种语言编写代码,所以需要把机器人的代码组织好,让其他开发者日后能轻松添加更多语言。
第一步,定义一个包含两个方法的接口Greeter。
LanguageName:返回机器人应当用来问候访客的语言名称(一个string)。Greet:接受访客的名字(一个string),并返回一个string,内容是某种特定语言的问候语。接下来,实现一个函数SayHello,它接受访客的名字以及任何实现了Greeter接口的东西作为参数,并返回所需的问候字符串。
例如,假设有一个德语的Greeter实现,它的LanguageName返回"German",Greet返回"Hallo {name}!":
SayHello("Dietrich", germanGreeter)
// => "I can speak German: Hallo Dietrich!"
现在你的任务是让机器人也能为扫描意大利护照的人服务。
为此,创建一个结构体Italian,并实现两个方法,让该结构体满足你在任务 1 中建立的Greeter接口。
你可以用"Ciao {name}!"向人打招呼。
在收工之前,你还应该完成用葡萄牙语问候别人的功能。
为此,创建一个结构体Portuguese,同样实现两个方法,让该结构体满足这里的Greeter接口。
你可以用"Olá {name}!"向人打招呼。