第十一章修改 (#839)

Co-authored-by: Joe Chen <jc@unknwon.io>
This commit is contained in:
Haigang Zhou
2022-05-12 21:55:24 +08:00
committed by GitHub
parent d3979794f4
commit e394361869
14 changed files with 86 additions and 89 deletions

View File

@@ -2,7 +2,7 @@
## 11.12.1 Go 的动态类型
在经典的面向对象语言(像 C++Java 和 C#)中数据和方法被封装为 `类` 的概念:类包含它们两者,并且不能剥离。
在经典的面向对象语言(像 C++Java 和 C#)中数据和方法被封装为*类*的概念:类包含它们两者,并且不能剥离。
Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。
@@ -10,11 +10,11 @@ Go 中的接口跟 Java/C# 类似:都是必须提供一个指定方法集的
和其它语言相比Go 是唯一结合了接口值,静态类型检查(是否该类型实现了某个接口),运行时动态转换的语言,并且不需要显式地声明类型是否满足某个接口。该特性允许我们在不改变已有的代码的情况下定义和使用新接口。
接收一个(或多个)接口类型作为参数的函数,其**实参**可以是任何实现了该接口的类型的变量。 `实现了某个接口的类型可以被传给任何以此接口为参数的函数`
接收一个(或多个)接口类型作为参数的函数,其**实参**可以是任何实现了该接口的类型的变量。 *实现了某个接口的类型可以被传给任何以此接口为参数的函数*
类似于 Python 和 Ruby 这类动态语言中的 `动态类型duck typing`;这意味着对象可以根据提供的方法被处理(例如,作为参数传递给函数),而忽略它们的实际类型:它们能做什么比它们是什么更重要。
类似于 Python 和 Ruby 这类动态语言中的动态类型 (duck typing);这意味着对象可以根据提供的方法被处理(例如,作为参数传递给函数),而忽略它们的实际类型:它们能做什么比它们是什么更重要。
这在程序 [duck_dance.go](examples/chapter_11/duck_dance.go) 中得以阐明,函数 DuckDance 接受一个 IDuck 接口类型变量。仅当 DuckDance 被实现了 IDuck 接口的类型调用时程序才能编译通过。
这在程序 [duck_dance.go](examples/chapter_11/duck_dance.go) 中得以阐明,函数 `DuckDance()` 接受一个 `IDuck` 接口类型变量。仅当 `DuckDance()` 被实现了 `IDuck` 接口的类型调用时程序才能编译通过。
示例 11.16 [duck_dance.go](examples/chapter_11/duck_dance.go)
@@ -77,7 +77,7 @@ cannot use b (type *Bird) as type IDuck in function argument:
像 PythonRuby 这类语言,动态类型是延迟绑定的(在运行时进行):方法只是用参数和变量简单地调用,然后在运行时才解析(它们很可能有像 `responds_to` 这样的方法来检查对象是否可以响应某个方法,但是这也意味着更大的编码量和更多的测试工作)
Go 的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 `interface{}` 这样的“泛型”上,你可以通过类型断言(参见 11.3 节)来检查变量是否实现了相应接口。
Go 的实现与此相反,通常需要编译器静态检查的支持:当变量被赋值给一个接口类型的变量时,编译器会检查其是否实现了该接口的所有函数。如果方法调用作用于像 `interface{}` 这样的“泛型”上,你可以通过类型断言(参见 [11.3](11.3.md) 节)来检查变量是否实现了相应接口。
例如,你用不同的类型表示 XML 输出流中的不同实体。然后我们为 XML 定义一个如下的“写”接口(甚至可以把它定义为私有接口):
@@ -87,7 +87,7 @@ type xmlWriter interface {
}
```
现在我们可以实现适用于该流类型的任何变量的 `StreamXML` 函数,并用类型断言检查传入的变量是否实现了该接口;如果没有,我们就调用内建的 `encodeToXML` 来完成相应工作:
现在我们可以实现适用于该流类型的任何变量的 `StreamXML()` 函数,并用类型断言检查传入的变量是否实现了该接口;如果没有,我们就调用内建的 `encodeToXML()` 来完成相应工作:
```go
// Exported XML streaming function.
@@ -112,15 +112,15 @@ Go 在这里用了和 `gob` 相同的机制:定义了两个接口 `GobEncoder`
对于动态语言非常重要的单元测试来说,这样即可以减少单元测试的部分需求,又可以发挥相当大的作用。
Go 的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。用 Go 接口还能实现 `依赖注入模式`
Go 的接口提高了代码的分离度,改善了代码的复用性,使得代码开发过程中的设计模式更容易实现。用 Go 接口还能实现依赖注入模式
## 11.12.3 接口的提取
`提取接口` 是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。
*提取接口*是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。
Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。
我们来扩展 11.1 节的示例 11.2 [interfaces_poly.go](examples/chapter_11/interfaces_poly.go),假设我们需要一个新的接口 `TopologicalGenus`,用来给 shape 排序(这里简单地实现为返回 int。我们需要做的是给想要满足接口的类型实现 `Rank()` 方法:
我们来扩展 11.1 节的示例 11.2 [interfaces_poly.go](examples/chapter_11/interfaces_poly.go),假设我们需要一个新的接口 `TopologicalGenus`,用来给 `shape` 排序(这里简单地实现为返回 `int`)。我们需要做的是给想要满足接口的类型实现 `Rank()` 方法:
示例 11.17 [multi_interfaces_poly.go](examples/chapter_11/multi_interfaces_poly.go)
@@ -195,7 +195,7 @@ Shape details: &{5}
Topological Genus of this shape is: 1
```
所以你不用提前设计出所有的接口;`整个设计可以持续演进,而不用废弃之前的决定`。类型要实现某个接口,它本身不用改变,你只需要在这个类型上实现新的方法。
所以你不用提前设计出所有的接口;*整个设计可以持续演进,而不用废弃之前的决定*。类型要实现某个接口,它本身不用改变,你只需要在这个类型上实现新的方法。
## 11.12.4 显式地指明类型实现了某个接口
@@ -222,7 +222,7 @@ func (b Bar) Foo() {}
## 11.12.5 空接口和函数重载
在 6.1 节中, 我们看到函数重载是不被允许的。在 Go 语言中函数重载可以用可变参数 `...T` 作为函数最后一个参数来实现(参见 6.3 节)。如果我们把 T 换为空接口,那么可以知道任何类型的变量都是满足 T (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。
[6.1](06.1.md) 节中, 我们看到函数重载是不被允许的。在 Go 语言中函数重载可以用可变参数 `...T` 作为函数最后一个参数来实现(参见 [6.3](06.3.md) 节)。如果我们把 `T` 换为空接口,那么可以知道任何类型的变量都是满足 `T` (空接口)类型的,这样就允许我们传递任何数量任何类型的参数给函数,即重载的实际含义。
函数 `fmt.Printf` 就是这样做的:
@@ -230,7 +230,7 @@ func (b Bar) Foo() {}
fmt.Printf(format string, a ...interface{}) (n int, errno error)
```
这个函数通过枚举 `slice` 类型的实参动态确定所有参数的类型,并查看每个类型是否实现了 `String()` 方法,如果是就用于产生输出信息。我们可以回到 11.10 节查看这些细节。
这个函数通过枚举 slice 类型的实参动态确定所有参数的类型,并查看每个类型是否实现了 `String()` 方法,如果是就用于产生输出信息。我们可以回到 [11.10](11.10.md) 节查看这些细节。
## 11.12.6 接口的继承
@@ -253,13 +253,13 @@ func NewTask(command string, logger *log.Logger) *Task {
}
```
`log.Logger` 实现了 `Log()` 方法后Task 的实例 task 就可以调用该方法:
`log.Logger` 实现了 `Log()` 方法后,`Task` 的实例 `task` 就可以调用该方法:
```go
task.Log()
```
类型可以通过继承多个接口来提供像 `多重继承` 一样的特性:
类型可以通过继承多个接口来提供像*多重继承*一样的特性:
```go
type ReaderWriter struct {
@@ -268,25 +268,25 @@ type ReaderWriter struct {
}
```
上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少(参见 12.8 节)。这被认为是 Go 编程中的重要的最佳实践。
上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少(参见 [12.8 节](12.8.md))。这被认为是 Go 编程中的重要的最佳实践。
有用的接口可以在开发的过程中被归纳出来。添加新接口非常容易,因为已有的类型不用变动(仅仅需要实现新接口的方法)。已有的函数可以扩展为使用接口类型的约束性参数:通常只有函数签名需要改变。对比基于类的 OO 类型的语言在这种情况下则需要适应整个类层次结构的变化。
**练习 11.11**[map_function_interface.go](exercises/chapter_11/map_function_interface.go)
在练习 7.13 中我们定义了一个 map 函数来使用 int 切片 map_function.go
在练习 7.13 中我们定义了一个 `map()` 函数来使用 `int` 切片 ([map_function.go](exercises\chapter_7\map_function.go))
通过空接口和类型断言,现在我们可以写一个可以应用于许多类型的 `泛型` 的 map 函数,为 int 和 string 构建一个把 int 值加倍和将字符串值与其自身连接(译者注:即 `"abc"` 变成 `"abcabc"` )的 map 函数 `mapFunc`
提示:为了可读性可以定义一个 interface{} 的别名比如type obj interface{}
通过空接口和类型断言,现在我们可以写一个可以应用于许多类型的*泛型*的 `map()` 函数,为 `int``string` 构建一个把 `int` 值加倍和将字符串值与其自身连接(译者注:即 `"abc"` 变成 `"abcabc"` )的 `map()` 函数 `mapFunc()`
提示:为了可读性可以定义一个 `interface{}` 的别名,比如:`type obj interface{}`
**练习 11.12**[map_function_interface_var.go](exercises/chapter_11/map_function_interface_var.go)
稍微改变练习 11.11,允许 `mapFunc` 接收不定数量的 items。
稍微改变练习 11.11,允许 `mapFunc()` 接收不定数量的 `items`
**练习 11.13**[main_stack.go](exercises/chapter_11/main_stack.go)—[stack/stack_general.go](exercises/chapter_11/stack/stack_general.go)
在练习 10.16 和 10.17 中我们开发了一些栈结构类型。但是它们被限制为某种固定的内建类型。现在用一个元素类型是 interface{}(空接口)的切片开发一个通用的栈类型。
在练习 10.16 和 10.17 中我们开发了一些栈结构类型。但是它们被限制为某种固定的内建类型。现在用一个元素类型是 `interface{}`(空接口)的切片开发一个通用的栈类型。
实现下面的栈方法: