mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 03:06:41 +08:00
11.2
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
## 翻译进度
|
## 翻译进度
|
||||||
|
|
||||||
11.11 [Printf 和反射](eBook/11.11.md)
|
11.12 [接口与动态类型](eBook/11.12.md)
|
||||||
|
|
||||||
## 支持本书
|
## 支持本书
|
||||||
|
|
||||||
|
@@ -30,4 +30,4 @@ Golang 编程:245386165
|
|||||||
|
|
||||||
|更新日期 |更新内容
|
|更新日期 |更新内容
|
||||||
|----------|------------------
|
|----------|------------------
|
||||||
|2015-10-31|11.11 Printf 和反射
|
|2015-11-07|11.12 接口与动态类型
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
# 11.12 接口与动态类型
|
# 11.12 接口与动态类型
|
||||||
|
|
||||||
## 11.12.1 Go的动态类型
|
## 11.12.1 Go 的动态类型
|
||||||
|
|
||||||
在经典的 OO 语言(像 C++,Java 和 C#)中数据和方法被封装为 `类的概念`:类包含它们两者,并且不能剥离。
|
在经典的面向对象语言(像 C++,Java 和 C#)中数据和方法被封装为 `类的概念`:类包含它们两者,并且不能剥离。
|
||||||
|
|
||||||
Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。
|
Go 没有类:数据(结构体或更一般的类型)和方法是一种松耦合的正交关系。
|
||||||
|
|
||||||
@@ -51,17 +51,19 @@ func main() {
|
|||||||
b := new(Bird)
|
b := new(Bird)
|
||||||
DuckDance(b)
|
DuckDance(b)
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
/** Output:
|
输出:
|
||||||
I am quacking!
|
|
||||||
I am walking!
|
|
||||||
I am quacking!
|
|
||||||
I am walking!
|
|
||||||
I am quacking!
|
|
||||||
I am walking!
|
|
||||||
*/
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
I am quacking!
|
||||||
|
I am walking!
|
||||||
|
I am quacking!
|
||||||
|
I am walking!
|
||||||
|
I am quacking!
|
||||||
|
I am walking!
|
||||||
|
```
|
||||||
|
|
||||||
如果 `Bird` 没有实现 `Walk()`(把它注释掉),会得到一个编译错误:
|
如果 `Bird` 没有实现 `Walk()`(把它注释掉),会得到一个编译错误:
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -83,7 +85,6 @@ Go 的实现与此相反,通常需要编译器静态检查的支持:当变
|
|||||||
type xmlWriter interface {
|
type xmlWriter interface {
|
||||||
WriteXML(w io.Writer) error
|
WriteXML(w io.Writer) error
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
现在我们可以实现适用于该流类型的任何变量的 `StreamXML` 函数,并用类型断言检查传入的变量是否实现了该接口;如果没有,我们就调用内建的 `encodeToXML` 来完成相应工作:
|
现在我们可以实现适用于该流类型的任何变量的 `StreamXML` 函数,并用类型断言检查传入的变量是否实现了该接口;如果没有,我们就调用内建的 `encodeToXML` 来完成相应工作:
|
||||||
@@ -103,7 +104,6 @@ func StreamXML(v interface{}, w io.Writer) error {
|
|||||||
func encodeToXML(v interface{}, w io.Writer) error {
|
func encodeToXML(v interface{}, w io.Writer) error {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Go 在这里用了和 `gob` 相同的机制:定义了两个接口 `GobEncoder` 和 `GobDecoder`。这样就允许类型自己实现从流编解码的具体方式;如果没有实现就使用标准的反射方式。
|
Go 在这里用了和 `gob` 相同的机制:定义了两个接口 `GobEncoder` 和 `GobDecoder`。这样就允许类型自己实现从流编解码的具体方式;如果没有实现就使用标准的反射方式。
|
||||||
@@ -116,7 +116,7 @@ Go 的接口提高了代码的分离度,改善了代码的复用性,使得
|
|||||||
|
|
||||||
## 11.12.3 接口的提取
|
## 11.12.3 接口的提取
|
||||||
|
|
||||||
`提取接口` 是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的 OO 语言那样维护整个的类层次结构。
|
`提取接口` 是非常有用的设计模式,可以减少需要的类型和方法数量,而且不需要像传统的基于类的面向对象语言那样维护整个的类层次结构。
|
||||||
|
|
||||||
Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。
|
Go 接口可以让开发者找出自己写的程序中的类型。假设有一些拥有共同行为的对象,并且开发者想要抽象出这些行为,这时就可以创建一个接口来使用。
|
||||||
我们来扩展 11.1 节的示例 11.2 interfaces_poly.go,假设我们需要一个新的接口 `TopologicalGenus`,用来给 shape 排序(这里简单地实现为返回 int)。我们需要做的是给想要满足接口的类型实现 `Rank()` 方法:
|
我们来扩展 11.1 节的示例 11.2 interfaces_poly.go,假设我们需要一个新的接口 `TopologicalGenus`,用来给 shape 排序(这里简单地实现为返回 int)。我们需要做的是给想要满足接口的类型实现 `Rank()` 方法:
|
||||||
@@ -177,7 +177,11 @@ func main() {
|
|||||||
fmt.Println("Topological Genus of this shape is: ", topgen[n].Rank())
|
fmt.Println("Topological Genus of this shape is: ", topgen[n].Rank())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Output:
|
```
|
||||||
|
|
||||||
|
输出:
|
||||||
|
|
||||||
|
```
|
||||||
Looping through shapes for area ...
|
Looping through shapes for area ...
|
||||||
Shape details: {5 3}
|
Shape details: {5 3}
|
||||||
Area of this shape is: 15
|
Area of this shape is: 15
|
||||||
@@ -188,8 +192,6 @@ Shape details: {5 3}
|
|||||||
Topological Genus of this shape is: 2
|
Topological Genus of this shape is: 2
|
||||||
Shape details: &{5}
|
Shape details: &{5}
|
||||||
Topological Genus of this shape is: 1
|
Topological Genus of this shape is: 1
|
||||||
*/
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
所以你不用提前设计出所有的接口;`整个设计可以持续演进,而不用废弃之前的决定`。类型要实现某个接口,它本身不用改变,你只需要在这个类型上实现新的方法。
|
所以你不用提前设计出所有的接口;`整个设计可以持续演进,而不用废弃之前的决定`。类型要实现某个接口,它本身不用改变,你只需要在这个类型上实现新的方法。
|
||||||
@@ -203,7 +205,6 @@ type Fooer interface {
|
|||||||
Foo()
|
Foo()
|
||||||
ImplementsFooer()
|
ImplementsFooer()
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
类型 Bar 必须实现 `ImplementsFooer` 方法来满足 `Footer` 接口,以清楚地记录这个事实。
|
类型 Bar 必须实现 `ImplementsFooer` 方法来满足 `Footer` 接口,以清楚地记录这个事实。
|
||||||
@@ -211,7 +212,6 @@ type Fooer interface {
|
|||||||
```go
|
```go
|
||||||
type Bar struct{}
|
type Bar struct{}
|
||||||
func (b Bar) ImplementsFooer() {} func (b Bar) Foo() {}
|
func (b Bar) ImplementsFooer() {} func (b Bar) Foo() {}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
大部分代码并不使用这样的约束,因为它限制了接口的实用性。
|
大部分代码并不使用这样的约束,因为它限制了接口的实用性。
|
||||||
@@ -226,7 +226,6 @@ func (b Bar) ImplementsFooer() {} func (b Bar) Foo() {}
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
fmt.Printf(format string, a ...interface{}) (n int, errno error)
|
fmt.Printf(format string, a ...interface{}) (n int, errno error)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
这个函数通过枚举 `slice` 类型的实参动态确定所有参数的类型。并查看每个类型是否实现了 `String()` 方法,如果是就用于产生输出信息。我们可以回到 11.10 节查看这些细节。
|
这个函数通过枚举 `slice` 类型的实参动态确定所有参数的类型。并查看每个类型是否实现了 `String()` 方法,如果是就用于产生输出信息。我们可以回到 11.10 节查看这些细节。
|
||||||
@@ -242,7 +241,6 @@ type Task struct {
|
|||||||
Command string
|
Command string
|
||||||
*log.Logger
|
*log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
这个类型的工厂方法像这样:
|
这个类型的工厂方法像这样:
|
||||||
@@ -251,14 +249,12 @@ type Task struct {
|
|||||||
func NewTask(command string, logger *log.Logger) *Task {
|
func NewTask(command string, logger *log.Logger) *Task {
|
||||||
return &Task{command, logger}
|
return &Task{command, logger}
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
当 `log.Logger` 实现了 `Log()` 方法后,Task 的实例 task 就可以调用该方法:
|
当 `log.Logger` 实现了 `Log()` 方法后,Task 的实例 task 就可以调用该方法:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
task.Log()
|
task.Log()
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
类型可以通过继承多个接口来提供像 `多重继承` 一样的特性:
|
类型可以通过继承多个接口来提供像 `多重继承` 一样的特性:
|
||||||
@@ -268,7 +264,6 @@ type ReaderWriter struct {
|
|||||||
*io.Reader
|
*io.Reader
|
||||||
*io.Writer
|
*io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少(参见 12.8 节)。这被认为是 Go 编程中的重要的最佳实践。
|
上面概述的原理被应用于整个 Go 包,多态用得越多,代码就相对越少(参见 12.8 节)。这被认为是 Go 编程中的重要的最佳实践。
|
||||||
@@ -298,14 +293,12 @@ Len() int
|
|||||||
IsEmpty() bool
|
IsEmpty() bool
|
||||||
Push(x interface{})
|
Push(x interface{})
|
||||||
Pop() (x interface{}, error)
|
Pop() (x interface{}, error)
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
`Pop()` 改变栈并返回最顶部的元素;`Top()` 只返回最顶部元素。
|
`Pop()` 改变栈并返回最顶部的元素;`Top()` 只返回最顶部元素。
|
||||||
|
|
||||||
在主程序中构建一个充满不同类型元素的栈,然后弹出并打印所有元素的值。
|
在主程序中构建一个充满不同类型元素的栈,然后弹出并打印所有元素的值。
|
||||||
|
|
||||||
|
|
||||||
## 链接
|
## 链接
|
||||||
|
|
||||||
- [目录](directory.md)
|
- [目录](directory.md)
|
||||||
|
Reference in New Issue
Block a user