![avatar](https://raw.githubusercontent.com/studygolang/gctt-images/master/interface-in-go/part1-1.jpg)
接口提升了代码的弹性与拓展性,同时它也是 go 语言实现多态的一种方式。接口允许通过一些必要的行为来实现,而不再要求设置特定类型。而这个行为就是通过一些方法设置来定义的:
```go
type I interface {
f1(name string)
f2(name string) (error, float32)
f3() int64
}
```
不需要特定的实现。只要通过定义 type 中包含目标名与签名(输入与输出的参数列表)的方法来表明其实现(满足)了一个接口就足够了:
```go
type T int64
func (T) f1(name string) {
fmt.Println(name)
}
func (T) f2(name string) (error, float32) {
return nil, 10.2
}
func (T) f3() int64 {
return 10
}
```
类型 T 实现了第一个程序段的接口 I。举个例子,类型 T 的值可以传递给任何接受 I 作为参数的函数([源代码](https://play.golang.org/p/aUyEa-HgYi)):
```go
type I interface {
M() string
}
type T struct {
name string
}
func (t T) M() string {
return t.name
}
func Hello(i I) {
fmt.Printf("Hi, my name is %s\n", i.M())
}
func main() {
Hello(T{name: "Michał"}) // "Hi, my name is Michał"
}
```
在 function Hello 中,方法调用了 `i.M()`。 这个过程概括一下就是,只要来自不同 type 的方法是通过 type 来实现 interface I,就可以被调用。
go 语言的突出特点就是其 `interface` 是隐式实现的。程序员不需要指定 type T 实现了 interface I。这个工作由 go 的编译器完成(不需要派一个人去做机器的工作)。这种行为中的实现方式之所以很赞,是因为定义 interface 这件事情是由已经写好的 type 自动实现的(不需要为之做任何改变)。
之所以 interface 可以提供弹性,是因为任意一个 type 可以实现多个 interface ([代码](https://play.golang.org/p/cN6KrJab-l)):
```go
type I1 interface {
M1()
}
type I2 interface {
M2()
}
type T struct{}
func (T) M1() { fmt.Println("T.M1") }
func (T) M2() { fmt.Println("T.M2") }
func f1(i I1) { i.M1() }
func f2(i I2) { i.M2() }
func main() {
t := T{}
f1(t) // "T.M1"
f2(t) // "T.M2"
}
```
或者同样的 interface 可以实现多个 type ([源代码](https://play.golang.org/p/_7mkHdEilz)):
```go
type I interface {
M()
}
type T1 struct{}
func (T1) M() { fmt.Println("T1.M") }
type T2 struct{}
func (T2) M() { fmt.Println("T2.M") }
func f(i I) { i.M() }
func main() {
f(T1{}) // "T1.M"
f(T2{}) // "T2.M"
}
```
> *而且除了一个或多个 interface 所需要的方法外,type 可以自由地实现其他方法*
---
在 go 中,我们有两个与 interface 相关的概念:
1. 接口-通过[关键字](https://golang.org/ref/spec#Keywords) `interface`,实现此类接口所需要的一组方法;
2. 接口类型-接口类型的变量,可以保存一些实现于特定接口的值。
让我们在接下来的两节中讨论这些主题。
## 定义一个接口
接口类型的声明指定属于它(接口)的方法。方法是通过它的名字(方法名)和签名-输入和接口参数定义的:
```go
type I interface {
m1()
m2(int)
m3(int) int
m4() int
}
```
除了方法外,它还允许嵌入其他接口-在同一个包中定义或引入-通过[限定名](https://golang.org/ref/spec#QualifiedIdent)。它从嵌入的接口中添加所有方法:
```go
import "fmt"
type I interface {
m1()
}
type J interface {
m2()
I
fmt.Stringer
}
```
接口 J 的方法组包括:
* m1() 来自嵌入的接口 I
* m2()
* String() string(来自嵌入的接口 [Stringer](https://golang.org/pkg/fmt/#Stringer))
顺序无关紧要,所以方法规格与嵌入的接口类型完全可以交错。
> *添加了来自嵌入接口类型的导出方法(以大写字母开头)和非导出方法(以小写字母开头)*
如果我嵌入一个接口 J,接口 J 又嵌入接口 K,那么 K 中的所有方法也会被添加到 I 中:
```go
type I interface {
J
i()
}
type J interface {
K
j()
}
type K interface {
k()
}
```
I 的方法组包括 `i()` ,`j()` 和 `k()` ([源代码](https://play.golang.org/p/mz_8CMMDsn))。
不允许循环嵌入接口(译注:即 A 嵌入 B,B 嵌入 C,C 嵌入 A),并且在编译阶段,会检测接口的循环嵌入问题([源代码](https://play.golang.org/p/CXf3-quH0A)):
```go
type I interface {
J
i()
}
type J interface {
K
j()
}
type K interface {
k()
I
}
```
编译器会提出一个错误 `interface type loop involving I`。
接口方法必须有唯一名字([源代码](https://play.golang.org/p/zt3t-GUrYU)):
```go
type I interface {
J
i()
}
type J interface {
j()
i(int)
}
```
否则将抛出编译时间错误:`duplicate method i`。
接口的组成可以在标准库中找到。一个这样的例子就是 io.ReadWriter :
```go
type ReadWriter interface {
Reader
Writer
}
```
我们知道如何创建一个新的接口。现在让我们学习接口类型的值...
## 接口类型的值
接口类型 I 的变量可以保持任何实现 I 的值([源代码](https://play.golang.org/p/Zvaq5c97wp)):
```go
type I interface {
method1()
}
type T struct{}
func (T) method1() {}
func main() {
var i I = T{}
fmt.Println(i)
}
```
这里我们有一个来自接口类型 I 的变量 i。
### 静态类型 VS 动态类型
在编译阶段,变量类型便已知。这是在声明时指定的,不再变化,并被称为静态类型(或只是类型)。接口类型的变量也有静态类型,其本身就是一个接口。它们还具有可以指定值的类型-动态类型([源代码](https://play.golang.org/p/UVMqqMNsb8)):
```go
type I interface {
M()
}
type T1 struct {}
func (T1) M() {}
type T2 struct {}
func (T2) M() {}
func main() {
var i I = T1{}
i = T2{}
_ = i
}
```
变量 i 的静态类型是 I。这是不变的。另一方面,动态类型是...好吧,动态的。在首次分配后,i 的动态类型是 T1。这并不是一成不变的,所以 i 的动态类型第二次赋值为 T2。当接口类型值的值 nil (接口类型的零值)时,动态类型便不设置。
### 如何获取接口类型值得动态类型?
包 [reflect](https://golang.org/pkg/reflect/)可以用来获取这个([源代码](https://play.golang.org/p/9cQ5JqSxL5)):
```go
fmt.Println(reflect.TypeOf(i).PkgPath(), reflect.TypeOf(i).Name())
fmt.Println(reflect.TypeOf(i).String())
```
通过包 [fmt](https://golang.org/pkg/fmt/) 以及格式动词 `%d` 也可以做到这点:
```go
fmt.Printf("%T\n", i)
```
在 hood 下使用包 *reflect* 包,即便 i 是 nil 时,这个方法也有效。
### 空接口值
这次我们将从一个例子开始([源代码](https://play.golang.org/p/kv9XUzIxBU)):
```go
type I interface {
M()
}
type T struct {}
func (T) M() {}
func main() {
var t *T
if t == nil {
fmt.Println("t is nil")
} else {
fmt.Println("t is not nil")
}
var i I = t
if i == nil {
fmt.Println("i is nil")
} else {
fmt.Println("i is not nil")
}
}
```
输出:
```
t is nil
i is not nil
```
第一次看,会觉得很惊讶。变量 i 的值,我们明明设置为 nil,但是这里的值却不等于 nil。其实接口类型值包含两个组件:
* 动态类型
* 动态值
动态类型在之前(“静态类型VS动态类型”部分)已经讨论过了。动态值是指定的实际值。
在赋值 `var i I = t` 后的讨论段中,i 的动态值是 nil,但动态类型为\**T*在这个复制后,函数调用 `fmt.Printf("%T\n", i)`将会打印 `*main.T`。`当且仅当动态值与动态类型都为 nil 时,接口类型值为 nil。`结果就是即使接口类型值包含一个 nil 指针,这样的接口值也不是 nil。已知的错误就是返回未初始化,从函数返回接口类型为非接口类型值([源代码](https://play.golang.org/p/4-M35Nc2JZ)):
```go
type I interface {}
type T struct {}
func F() I {
var t *T
if false { // not reachable but it actually sets value
t = &T{}
}
return t
}
func main() {
fmt.Printf("F() = %v\n", F())
fmt.Printf("F() is nil: %v\n", F() == nil)
fmt.Printf("type of F(): %T", F())
}
```
它打印出:
```
F() = <nil>
F() is nil: false
type of F(): *main.T
```
只是因为从函数返回的接口类型值有动态类型集(*main.T)。它并不等于 nil。
### 空接口
接口的方法集不必包含至少一个成员(即方法集为空)。它完全可以是空的([源代码](https://play.golang.org/p/V0GEG5nuW3)):
```go
type I interface {}
type T struct {}
func (T) M() {}
func main() {
var i I = T{}
_ = i
}
```
空接口可以自动满足任意类型-因此任意类型的值都可以赋值给这样的接口类型值。动态类型或静态类型的行为应用于空接口,就像应用于非空接口。
空接口的显著使用存在于参数可变函数 [fmt.Println](https://golang.org/pkg/fmt/#Println)。
---
## 满足一个接口
每个实现了接口所有方法的类型都自动满足这个接口。我们不需要在这些类型中使用任何其他关键字(如 Java中的 implements)来表示该类型实现了接口。它是由 go 语言的编译器自动实现的,而这儿正是该语言的强大之处([源代码](https://play.golang.org/p/U4r6i2X5xb)):
```go
import (
"fmt"
"regexp"
)
type I interface {
Find(b []byte) []byte
}
func f(i I) {
fmt.Printf("%s\n", i.Find([]byte("abc")))
}
func main() {
var re = regexp.MustCompile(`b`)
f(re)
}
```
这里我们定义了一个由 [regexp.Regexp](https://golang.org/pkg/regexp/#Regexp) 类型实现的接口,该接口内置的 regexp 模块没有任何改变。
## 行为抽象
接口类型值**只**允许访问它自己的接口类型的方法。如果它是 struct, array, scalar 等,便会隐藏有关确切值的详情([源代码](https://play.golang.org/p/kCjgQFCsL_)):
```go
type I interface {
M1()
}
type T int64
func (T) M1() {}
func (T) M2() {}
func main() {
var i I = T(10)
i.M1()
i.M2() // i.M2 undefined (type I has no field or method M2)
}
```
via: https://medium.com/golangspec/interfaces-in-go-part-i-4ae53a97479c
作者:Michał Łowicki 译者:cureking 校对:polaris1119
本文由 GCTT 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。
欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽
有疑问加站长微信联系(非本文作者))