|
@@ -2,15 +2,15 @@
|
|
|
|
|
|
|
|
## 10.6.1 方法是什么
|
|
## 10.6.1 方法是什么
|
|
|
|
|
|
|
|
-在 Go 语言中,结构体就像是类的一种简化形式,那么面向对象程序员可能会问:类的方法在哪里呢?在 Go 中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收者(receiver)上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。
|
|
|
|
|
|
|
+在 Go 语言中,结构体就像是类的一种简化形式,那么面向对象程序员可能会问:类的方法在哪里呢?在 Go 中有一个概念,它和方法有着同样的名字,并且大体上意思相同:Go 方法是作用在接收者 (receiver) 上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数。
|
|
|
|
|
|
|
|
-接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名类型。但是接收者不能是一个接口类型(参考 第 11 章),因为接口是一个抽象定义,但是方法却是具体实现;如果这样做会引发一个编译错误:**invalid receiver type…**。
|
|
|
|
|
|
|
+接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 `int`、`bool`、`string` 或数组的别名类型。但是接收者不能是一个接口类型(参考[第 11 章](11.0.md)),因为接口是一个抽象定义,但是方法却是具体实现;如果这样做会引发一个编译错误:`invalid receiver type...`。
|
|
|
|
|
|
|
|
最后接收者不能是一个指针类型,但是它可以是任何其他允许类型的指针。
|
|
最后接收者不能是一个指针类型,但是它可以是任何其他允许类型的指针。
|
|
|
|
|
|
|
|
一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。
|
|
一个类型加上它的方法等价于面向对象中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。
|
|
|
|
|
|
|
|
-类型 T(或 \*T)上的所有方法的集合叫做类型 T(或 \*T)的方法集(method set)。
|
|
|
|
|
|
|
+类型 `T`(或 `*T`)上的所有方法的集合叫做类型 `T`(或 `*T`)的方法集 (method set)。
|
|
|
|
|
|
|
|
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么做是允许的:
|
|
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么做是允许的:
|
|
|
|
|
|
|
@@ -29,7 +29,7 @@ func (recv receiver_type) methodName(parameter_list) (return_value_list) { ... }
|
|
|
|
|
|
|
|
在方法名之前,`func` 关键字之后的括号中指定 receiver。
|
|
在方法名之前,`func` 关键字之后的括号中指定 receiver。
|
|
|
|
|
|
|
|
-如果 `recv` 是 receiver 的实例,Method1 是它的方法名,那么方法调用遵循传统的 `object.name` 选择器符号:**recv.Method1()**。
|
|
|
|
|
|
|
+如果 `recv` 是 receiver 的实例,`Method1` 是它的方法名,那么方法调用遵循传统的 `object.name` 选择器符号:`recv.Method1()`。
|
|
|
|
|
|
|
|
如果 `recv` 是一个指针,Go 会自动解引用。
|
|
如果 `recv` 是一个指针,Go 会自动解引用。
|
|
|
|
|
|
|
@@ -103,11 +103,11 @@ func main() {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-**练习 10.6** employee_salary.go
|
|
|
|
|
|
|
+**练习 10.6** [employee_salary.go](exercises\chapter_10\employee_salary.go)
|
|
|
|
|
|
|
|
定义结构体 `employee`,它有一个 `salary` 字段,给这个结构体定义一个方法 `giveRaise` 来按照指定的百分比增加薪水。
|
|
定义结构体 `employee`,它有一个 `salary` 字段,给这个结构体定义一个方法 `giveRaise` 来按照指定的百分比增加薪水。
|
|
|
|
|
|
|
|
-**练习 10.7** iteration_list.go
|
|
|
|
|
|
|
+**练习 10.7** [iteration_list.go](exercises\chapter_10\iteration_list.go)
|
|
|
|
|
|
|
|
下面这段代码有什么错?
|
|
下面这段代码有什么错?
|
|
|
|
|
|
|
@@ -127,7 +127,7 @@ func main() {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 int、float 或类似这些的类型上定义方法。试图在 int 类型上定义方法会得到一个编译错误:
|
|
|
|
|
|
|
+类型和作用在它上面定义的方法必须在同一个包里定义,这就是为什么不能在 `int`、`float32(64)` 或类似这些的类型上定义方法。试图在 `int` 类型上定义方法会得到一个编译错误:
|
|
|
|
|
|
|
|
cannot define new methods on non-local type int
|
|
cannot define new methods on non-local type int
|
|
|
|
|
|
|
@@ -141,7 +141,7 @@ func (t time.Time) first3Chars() string {
|
|
|
|
|
|
|
|
类型在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。
|
|
类型在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。
|
|
|
|
|
|
|
|
-但是有一个间接的方式:可以先定义该类型(比如:int 或 float)的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。
|
|
|
|
|
|
|
+但是有一个间接的方式:可以先定义该类型(比如:`int` 或 `float32(64)`)的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。
|
|
|
|
|
|
|
|
示例 10.12 [method_on_time.go](examples/chapter_10/method_on_time.go):
|
|
示例 10.12 [method_on_time.go](examples/chapter_10/method_on_time.go):
|
|
|
|
|
|
|
@@ -176,17 +176,17 @@ First 3 chars: Mon
|
|
|
|
|
|
|
|
## 10.6.2 函数和方法的区别
|
|
## 10.6.2 函数和方法的区别
|
|
|
|
|
|
|
|
-函数将变量作为参数:**Function1(recv)**
|
|
|
|
|
|
|
+函数将变量作为参数:`Function1(recv)`
|
|
|
|
|
|
|
|
-方法在变量上被调用:**recv.Method1()**
|
|
|
|
|
|
|
+方法在变量上被调用:`recv.Method1()`
|
|
|
|
|
|
|
|
在接收者是指针时,方法可以改变接收者的值(或状态),这点函数也可以做到(当参数作为指针传递,即通过引用调用时,函数也可以改变参数的状态)。
|
|
在接收者是指针时,方法可以改变接收者的值(或状态),这点函数也可以做到(当参数作为指针传递,即通过引用调用时,函数也可以改变参数的状态)。
|
|
|
|
|
|
|
|
-**不要忘记 Method1 后边的括号 (),否则会引发编译器错误:`method recv.Method1 is not an expression, must be called`**
|
|
|
|
|
|
|
+**不要忘记 `Method1()` 后边的括号 `()`,否则会引发编译器错误:`method recv.Method1 is not an expression, must be called`**
|
|
|
|
|
|
|
|
接收者必须有一个显式的名字,这个名字必须在方法中被使用。
|
|
接收者必须有一个显式的名字,这个名字必须在方法中被使用。
|
|
|
|
|
|
|
|
-**receiver_type** 叫做 **(接收者)基本类型**,这个类型必须在和方法同样的包中被声明。
|
|
|
|
|
|
|
+`receiver_type` 叫做 **(接收者)基本类型**,这个类型必须在和方法同样的包中被声明。
|
|
|
|
|
|
|
|
在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
|
|
在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
|
|
|
|
|
|
|
@@ -194,11 +194,11 @@ First 3 chars: Mon
|
|
|
|
|
|
|
|
## 10.6.3 指针或值作为接收者
|
|
## 10.6.3 指针或值作为接收者
|
|
|
|
|
|
|
|
-鉴于性能的原因,`recv` 最常见的是一个指向 receiver_type 的指针(因为我们不想要一个实例的拷贝,如果按值调用的话就会是这样),特别是在 receiver 类型是结构体时,就更是如此了。
|
|
|
|
|
|
|
+鉴于性能的原因,`recv` 最常见的是一个指向 `receiver_type` 的指针(因为我们不想要一个实例的拷贝,如果按值调用的话就会是这样),特别是在 receiver 类型是结构体时,就更是如此了。
|
|
|
|
|
|
|
|
如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。
|
|
如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。
|
|
|
|
|
|
|
|
-下面的例子 `pointer_value.go` 作了说明:`change()`接受一个指向 B 的指针,并改变它内部的成员;`write()` 通过拷贝接受 B 的值并只输出 B 的内容。注意 Go 为我们做了探测工作,我们自己并没有指出是否在指针上调用方法,Go 替我们做了这些事情。b1 是值而 b2 是指针,方法都支持运行了。
|
|
|
|
|
|
|
+下面的例子 `pointer_value.go` 作了说明:`change()`接受一个指向 `B` 的指针,并改变它内部的成员;`write()` 通过拷贝接受 `B` 的值并只输出 `B` 的内容。注意 Go 为我们做了探测工作,我们自己并没有指出是否在指针上调用方法,Go 替我们做了这些事情。`b1` 是值而 `b2` 是指针,方法都支持运行了。
|
|
|
|
|
|
|
|
示例 10.13 [pointer_value.go](examples/chapter_10/pointer_value.go):
|
|
示例 10.13 [pointer_value.go](examples/chapter_10/pointer_value.go):
|
|
|
|
|
|
|
@@ -233,7 +233,7 @@ func main() {
|
|
|
*/
|
|
*/
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-试着在 `write()` 中改变接收者 b 的值:将会看到它可以正常编译,但是开始的 b 没有被改变。
|
|
|
|
|
|
|
+试着在 `write()` 中改变接收者 `b` 的值:将会看到它可以正常编译,但是开始的 `b` 没有被改变。
|
|
|
|
|
|
|
|
我们知道方法将指针作为接收者不是必须的,如下面的例子,我们只是需要 `Point3` 的值来做计算:
|
|
我们知道方法将指针作为接收者不是必须的,如下面的例子,我们只是需要 `Point3` 的值来做计算:
|
|
|
|
|
|
|
@@ -251,7 +251,7 @@ func (p Point3) Abs() float64 {
|
|
|
|
|
|
|
|
可以使用 `p3.Abs()` 来替代 `(*p3).Abs()`。
|
|
可以使用 `p3.Abs()` 来替代 `(*p3).Abs()`。
|
|
|
|
|
|
|
|
-像例子 10.10([method1.go](examples/chapter_10/method1.go))中接收者类型是 `*TwoInts` 的方法 `AddThem()`,它能在类型 `TwoInts` 的值上被调用,这是自动间接发生的。
|
|
|
|
|
|
|
+像例子 10.10 ([method1.go](examples/chapter_10/method1.go)) 中接收者类型是 `*TwoInts` 的方法 `AddThem()`,它能在类型 `TwoInts` 的值上被调用,这是自动间接发生的。
|
|
|
|
|
|
|
|
因此 `two2.AddThem` 可以替代 `(&two2).AddThem()`。
|
|
因此 `two2.AddThem` 可以替代 `(&two2).AddThem()`。
|
|
|
|
|
|
|
@@ -259,7 +259,7 @@ func (p Point3) Abs() float64 {
|
|
|
|
|
|
|
|
可以有连接到类型的方法,也可以有连接到类型指针的方法。
|
|
可以有连接到类型的方法,也可以有连接到类型指针的方法。
|
|
|
|
|
|
|
|
-但是这没关系:对于类型 T,如果在 \*T 上存在方法 `Meth()`,并且 `t` 是这个类型的变量,那么 `t.Meth()` 会被自动转换为 `(&t).Meth()`。
|
|
|
|
|
|
|
+但是这没关系:对于类型 `T`,如果在 `\*T` 上存在方法 `Meth()`,并且 `t` 是这个类型的变量,那么 `t.Meth()` 会被自动转换为 `(&t).Meth()`。
|
|
|
|
|
|
|
|
**指针方法和值方法都可以在指针或非指针上被调用**,如下面程序所示,类型 `List` 在值上有一个方法 `Len()`,在指针上有一个方法 `Append()`,但是可以看到两个方法都可以在两种类型的变量上被调用。
|
|
**指针方法和值方法都可以在指针或非指针上被调用**,如下面程序所示,类型 `List` 在值上有一个方法 `Len()`,在指针上有一个方法 `Append()`,但是可以看到两个方法都可以在两种类型的变量上被调用。
|
|
|
|
|
|
|
@@ -294,7 +294,7 @@ func main() {
|
|
|
|
|
|
|
|
考虑 `person2.go` 中的 `person` 包:类型 `Person` 被明确的导出了,但是它的字段没有被导出。例如在 `use_person2.go` 中 `p.firstName` 就是错误的。该如何在另一个程序中修改或者只是读取一个 `Person` 的名字呢?
|
|
考虑 `person2.go` 中的 `person` 包:类型 `Person` 被明确的导出了,但是它的字段没有被导出。例如在 `use_person2.go` 中 `p.firstName` 就是错误的。该如何在另一个程序中修改或者只是读取一个 `Person` 的名字呢?
|
|
|
|
|
|
|
|
-这可以通过面向对象语言一个众所周知的技术来完成:提供 getter 和 setter 方法。对于 setter 方法使用 Set 前缀,对于 getter 方法只使用成员名。
|
|
|
|
|
|
|
+这可以通过面向对象语言一个众所周知的技术来完成:提供 `getter()` 和 `setter()` 方法。对于 `setter()` 方法使用 `Set...` 前缀,对于 `getter()` 方法只使用成员名。
|
|
|
|
|
|
|
|
示例 10.15 [person2.go](examples/chapter_10/person2.go):
|
|
示例 10.15 [person2.go](examples/chapter_10/person2.go):
|
|
|
|
|
|
|
@@ -315,7 +315,7 @@ func (p *Person) SetFirstName(newName string) {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-示例 10.16—[use_person2.go](examples/chapter_10/use_person2.go):
|
|
|
|
|
|
|
+示例 10.16 [use_person2.go](examples/chapter_10/use_person2.go):
|
|
|
|
|
|
|
|
```go
|
|
```go
|
|
|
package main
|
|
package main
|
|
@@ -337,11 +337,11 @@ func main() {
|
|
|
|
|
|
|
|
**并发访问对象**
|
|
**并发访问对象**
|
|
|
|
|
|
|
|
-对象的字段(属性)不应该由 2 个或 2 个以上的不同线程在同一时间去改变。如果在程序发生这种情况,为了安全并发访问,可以使用包 `sync`(参考第 9.3 节)中的方法。在第 14.17 节中我们会通过 goroutines 和 channels 探索另一种方式。
|
|
|
|
|
|
|
+对象的字段(属性)不应该由 2 个或 2 个以上的不同线程在同一时间去改变。如果在程序发生这种情况,为了安全并发访问,可以使用包 `sync`(参考[第 9.3 节](09.3.md)中的方法。在[第 14.17 节](14.17)中我们会通过 goroutines 和 channels 探索另一种方式。
|
|
|
|
|
|
|
|
## 10.6.5 内嵌类型的方法和继承
|
|
## 10.6.5 内嵌类型的方法和继承
|
|
|
|
|
|
|
|
-当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 **继承** 了这些方法:**将父类型放在子类型中来实现亚型**。这个机制提供了一种简单的方式来模拟经典面向对象语言中的子类和继承相关的效果,也类似 Ruby 中的混入(mixin)。
|
|
|
|
|
|
|
+当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这在效果上等同于外层类型 **继承** 了这些方法:**将父类型放在子类型中来实现亚型**。这个机制提供了一种简单的方式来模拟经典面向对象语言中的子类和继承相关的效果,也类似 Ruby 中的混入 (mixin)。
|
|
|
|
|
|
|
|
下面是一个示例(可以在练习 10.8 中进一步学习):假定有一个 `Engine` 接口类型,一个 `Car` 结构体类型,它包含一个 `Engine` 类型的匿名字段:
|
|
下面是一个示例(可以在练习 10.8 中进一步学习):假定有一个 `Engine` 接口类型,一个 `Car` 结构体类型,它包含一个 `Engine` 类型的匿名字段:
|
|
|
|
|
|
|
@@ -411,11 +411,11 @@ func (n *NamedPoint) Abs() float64 {
|
|
|
|
|
|
|
|
现在 `fmt.Println(n.Abs())` 会打印 `500`。
|
|
现在 `fmt.Println(n.Abs())` 会打印 `500`。
|
|
|
|
|
|
|
|
-因为一个结构体可以嵌入多个匿名类型,所以实际上我们可以有一个简单版本的多重继承,就像:`type Child struct { Father; Mother}`。在第 10.6.7 节中会进一步讨论这个问题。
|
|
|
|
|
|
|
+因为一个结构体可以嵌入多个匿名类型,所以实际上我们可以有一个简单版本的多重继承,就像:`type Child struct { Father; Mother}`。在[第 10.6.7 节](10.6.md)中会进一步讨论这个问题。
|
|
|
|
|
|
|
|
结构体内嵌和自己在同一个包中的结构体时,可以彼此访问对方所有的字段和方法。
|
|
结构体内嵌和自己在同一个包中的结构体时,可以彼此访问对方所有的字段和方法。
|
|
|
|
|
|
|
|
-**练习 10.8** inheritance_car.go
|
|
|
|
|
|
|
+**练习 10.8** [inheritance_car.go](exercises\chapter_10\inheritance_car.go)
|
|
|
|
|
|
|
|
创建一个上面 `Car` 和 `Engine` 可运行的例子,并且给 `Car` 类型一个 `wheelCount` 字段和一个 `numberOfWheels()` 方法。
|
|
创建一个上面 `Car` 和 `Engine` 可运行的例子,并且给 `Car` 类型一个 `wheelCount` 字段和一个 `numberOfWheels()` 方法。
|
|
|
|
|
|
|
@@ -433,7 +433,7 @@ B:内嵌:内嵌(匿名地)所需功能类型,像前一节 10.6.5 所
|
|
|
|
|
|
|
|
为了使这些概念具体化,假设有一个 `Customer` 类型,我们想让它通过 `Log` 类型来包含日志功能,`Log` 类型只是简单地包含一个累积的消息(当然它可以是复杂的)。如果想让特定类型都具备日志功能,你可以实现一个这样的 `Log` 类型,然后将它作为特定类型的一个字段,并提供 `Log()`,它返回这个日志的引用。
|
|
为了使这些概念具体化,假设有一个 `Customer` 类型,我们想让它通过 `Log` 类型来包含日志功能,`Log` 类型只是简单地包含一个累积的消息(当然它可以是复杂的)。如果想让特定类型都具备日志功能,你可以实现一个这样的 `Log` 类型,然后将它作为特定类型的一个字段,并提供 `Log()`,它返回这个日志的引用。
|
|
|
|
|
|
|
|
-方式 A 可以通过如下方法实现(使用了第 10.7 节中的 `String()` 功能):
|
|
|
|
|
|
|
+方式 A 可以通过如下方法实现(使用了[第 10.7 节](10.7.md)中的 `String()` 功能):
|
|
|
|
|
|
|
|
示例 10.19 [embed_func1.go](examples/chapter_10/embed_func1.go):
|
|
示例 10.19 [embed_func1.go](examples/chapter_10/embed_func1.go):
|
|
|
|
|
|
|
@@ -485,7 +485,7 @@ func (c *Customer) Log() *Log {
|
|
|
1 - Yes we can!
|
|
1 - Yes we can!
|
|
|
2 - After me the world will be a better place!
|
|
2 - After me the world will be a better place!
|
|
|
|
|
|
|
|
-相对的方式 B 可能会像这样([embed_func2.go](examples/chapter_10/embed_func2.go)):
|
|
|
|
|
|
|
+相对的方式 B 可能会像这样 ([embed_func2.go](examples/chapter_10/embed_func2.go)):
|
|
|
|
|
|
|
|
```go
|
|
```go
|
|
|
package main
|
|
package main
|
|
@@ -541,7 +541,7 @@ func (c *Customer) String() string {
|
|
|
|
|
|
|
|
作为一个例子,假设有一个类型 `CameraPhone`,通过它可以 `Call()`,也可以 `TakeAPicture()`,但是第一个方法属于类型 `Phone`,第二个方法属于类型 `Camera`。
|
|
作为一个例子,假设有一个类型 `CameraPhone`,通过它可以 `Call()`,也可以 `TakeAPicture()`,但是第一个方法属于类型 `Phone`,第二个方法属于类型 `Camera`。
|
|
|
|
|
|
|
|
-只要嵌入这两个类型就可以解决这个问题,如下所示([mult_inheritance.go](examples/chapter_10/mult_inheritance.go)):
|
|
|
|
|
|
|
+只要嵌入这两个类型就可以解决这个问题,如下所示 ([mult_inheritance.go](examples/chapter_10/mult_inheritance.go)):
|
|
|
|
|
|
|
|
```go
|
|
```go
|
|
|
package main
|
|
package main
|
|
@@ -581,17 +581,17 @@ func main() {
|
|
|
It exhibits behavior of a Camera: Click
|
|
It exhibits behavior of a Camera: Click
|
|
|
It works like a Phone too: Ring Ring
|
|
It works like a Phone too: Ring Ring
|
|
|
|
|
|
|
|
-**练习 10.9** point_methods.go:
|
|
|
|
|
|
|
+**练习 10.9** [point_methods.go](exercises\chapter_10\point_methods.go):
|
|
|
|
|
|
|
|
-从 `point.go` 开始(第 10.1 节的练习):使用方法来实现 `Abs()` 和 `Scale()`函数,`Point` 作为方法的接收者类型。也为 `Point3` 和 `Polar` 实现 `Abs()` 方法。完成了 `point.go` 中同样的事情,只是这次通过方法。
|
|
|
|
|
|
|
+从 `point.go` 开始([第 10.1 节](10.1)的练习):使用方法来实现 `Abs()` 和 `Scale()`函数,`Point` 作为方法的接收者类型。也为 `Point3` 和 `Polar` 实现 `Abs()` 方法。完成了 `point.go` 中同样的事情,只是这次通过方法。
|
|
|
|
|
|
|
|
-**练习 10.10** inherit_methods.go:
|
|
|
|
|
|
|
+**练习 10.10** [inherit_methods.go](exercises\chapter_10\inherit_methods.go):
|
|
|
|
|
|
|
|
定义一个结构体类型 `Base`,它包含一个字段 `id`,方法 `Id()` 返回 `id`,方法 `SetId()` 修改 `id`。结构体类型 `Person` 包含 `Base`,及 `FirstName` 和 `LastName` 字段。结构体类型 `Employee` 包含一个 `Person` 和 `salary` 字段。
|
|
定义一个结构体类型 `Base`,它包含一个字段 `id`,方法 `Id()` 返回 `id`,方法 `SetId()` 修改 `id`。结构体类型 `Person` 包含 `Base`,及 `FirstName` 和 `LastName` 字段。结构体类型 `Employee` 包含一个 `Person` 和 `salary` 字段。
|
|
|
|
|
|
|
|
创建一个 `employee` 实例,然后显示它的 `id`。
|
|
创建一个 `employee` 实例,然后显示它的 `id`。
|
|
|
|
|
|
|
|
-**练习 10.11** magic.go:
|
|
|
|
|
|
|
+**练习 10.11** [magic.go](exercises\chapter_10\magic.go):
|
|
|
|
|
|
|
|
首先预测一下下面程序的结果,然后动手实验下:
|
|
首先预测一下下面程序的结果,然后动手实验下:
|
|
|
|
|
|
|
@@ -630,7 +630,7 @@ func main() {
|
|
|
|
|
|
|
|
## 10.6.8 通用方法和方法命名
|
|
## 10.6.8 通用方法和方法命名
|
|
|
|
|
|
|
|
-在编程中一些基本操作会一遍又一遍的出现,比如打开(Open)、关闭(Close)、读(Read)、写(Write)、排序(Sort)等等,并且它们都有一个大致的意思:打开(Open)可以作用于一个文件、一个网络连接、一个数据库连接等等。具体的实现可能千差万别,但是基本的概念是一致的。在 Go 语言中,通过使用接口(参考 第 11 章),标准库广泛的应用了这些规则,在标准库中这些通用方法都有一致的名字,比如 `Open()`、`Read()`、`Write()`等。想写规范的 Go 程序,就应该遵守这些约定,给方法合适的名字和签名,就像那些通用方法那样。这样做会使 Go 开发的软件更加具有一致性和可读性。比如:如果需要一个 convert-to-string 方法,应该命名为 `String()`,而不是 `ToString()`(参考第 10.7 节)。
|
|
|
|
|
|
|
+在编程中一些基本操作会一遍又一遍的出现,比如打开 (Open)、关闭 (Close)、读 (Read)、写 (Write)、排序(Sort) 等等,并且它们都有一个大致的意思:打开 (Open)可以作用于一个文件、一个网络连接、一个数据库连接等等。具体的实现可能千差万别,但是基本的概念是一致的。在 Go 语言中,通过使用接口(参考[第 11 章](11.0.md)),标准库广泛的应用了这些规则,在标准库中这些通用方法都有一致的名字,比如 `Open()`、`Read()`、`Write()`等。想写规范的 Go 程序,就应该遵守这些约定,给方法合适的名字和签名,就像那些通用方法那样。这样做会使 Go 开发的软件更加具有一致性和可读性。比如:如果需要一个 `convert-to-string()` 方法,应该命名为 `String()`,而不是 `ToString()`(参考[第 10.7 节](10.7.md))。
|
|
|
|
|
|
|
|
## 10.6.9 和其他面向对象语言比较 Go 的类型和方法
|
|
## 10.6.9 和其他面向对象语言比较 Go 的类型和方法
|
|
|
|
|
|
|
@@ -640,7 +640,7 @@ func main() {
|
|
|
|
|
|
|
|
下面的模式就很好的说明了这个问题:
|
|
下面的模式就很好的说明了这个问题:
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
+<img src="images/10.6.9_fig10.4.jpg?raw=true" style="zoom:80%;" />
|
|
|
|
|
|
|
|
Go 不需要一个显式的类定义,如同 Java、C++、C# 等那样,相反地,“类”是通过提供一组作用于一个共同类型的方法集来隐式定义的。类型可以是结构体或者任何用户自定义类型。
|
|
Go 不需要一个显式的类定义,如同 Java、C++、C# 等那样,相反地,“类”是通过提供一组作用于一个共同类型的方法集来隐式定义的。类型可以是结构体或者任何用户自定义类型。
|
|
|
|
|
|
|
@@ -653,19 +653,19 @@ func (i *Integer) String() string {
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
-在 Java 或 C# 中,这个方法需要和类 `Integer` 的定义放在一起,在 Ruby 中可以直接在基本类型 int 上定义这个方法。
|
|
|
|
|
|
|
+在 Java 或 C# 中,这个方法需要和类 `Integer` 的定义放在一起,在 Ruby 中可以直接在基本类型 `int` 上定义这个方法。
|
|
|
|
|
|
|
|
**总结**
|
|
**总结**
|
|
|
|
|
|
|
|
在 Go 中,类型就是类(数据和关联的方法)。Go 不知道类似面向对象语言的类继承的概念。继承有两个好处:代码复用和多态。
|
|
在 Go 中,类型就是类(数据和关联的方法)。Go 不知道类似面向对象语言的类继承的概念。继承有两个好处:代码复用和多态。
|
|
|
|
|
|
|
|
-在 Go 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 **组件编程(Component Programming)**。
|
|
|
|
|
|
|
+在 Go 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 **组件编程 (Component Programming)**。
|
|
|
|
|
|
|
|
许多开发者说相比于类继承,Go 的接口提供了更强大、却更简单的多态行为。
|
|
许多开发者说相比于类继承,Go 的接口提供了更强大、却更简单的多态行为。
|
|
|
|
|
|
|
|
**备注**
|
|
**备注**
|
|
|
|
|
|
|
|
-如果真的需要更多面向对象的能力,看一下 [`goop`](https://github.com/losalamos/goop) 包(Go Object-Oriented Programming),它由 Scott Pakin 编写: 它给 Go 提供了 JavaScript 风格的对象(基于原型的对象),并且支持多重继承和类型独立分派,通过它可以实现你喜欢的其他编程语言里的一些结构。
|
|
|
|
|
|
|
+如果真的需要更多面向对象的能力,看一下 [`goop`](https://github.com/losalamos/goop) 包 (Go Object-Oriented Programming),它由 Scott Pakin 编写: 它给 Go 提供了 JavaScript 风格的对象(基于原型的对象),并且支持多重继承和类型独立分派,通过它可以实现你喜欢的其他编程语言里的一些结构。
|
|
|
|
|
|
|
|
**问题 10.1**
|
|
**问题 10.1**
|
|
|
|
|
|
|
@@ -675,7 +675,7 @@ func (i *Integer) String() string {
|
|
|
|
|
|
|
|
a)假设定义: `type Integer int`,完成 `get()` 方法的方法体: `func (p Integer) get() int { ... }`。
|
|
a)假设定义: `type Integer int`,完成 `get()` 方法的方法体: `func (p Integer) get() int { ... }`。
|
|
|
|
|
|
|
|
-b)定义: `func f(i int) {}; var v Integer` ,如何就 v 作为参数调用f?
|
|
|
|
|
|
|
+b)定义: `func f(i int) {}; var v Integer` ,如何就 `v` 作为参数调用f?
|
|
|
|
|
|
|
|
c)假设 `Integer` 定义为 `type Integer struct {n int}`,完成 `get()` 方法的方法体:`func (p Integer) get() int { ... }`。
|
|
c)假设 `Integer` 定义为 `type Integer struct {n int}`,完成 `get()` 方法的方法体:`func (p Integer) get() int { ... }`。
|
|
|
|
|
|