@@ -2,15 +2,15 @@
## 10.6.1 方法是什么
在Go中, 结构体就像是类的一种简化形式, 那么OO 程序员可能会问: 类的方法在哪里呢? 在Go中有一个概念, 它和方法有着同样的名字, 并且大体上意思相同: Go方法是作用在接收者( receiver) 上的一个函数, 接收者是某种类型的变量。因此方法是一种特殊类型的函数。
在 Go 语言 中,结构体就像是类的一种简化形式,那么面向对象 程序员可能会问:类的方法在哪里呢?在 Go 中有一个概念, 它和方法有着同样的名字, 并且大体上意思相同: Go 方法是作用在接收者( receiver) 上的一个函数, 接收者是某种类型的变量。因此方法是一种特殊类型的函数。
接收者类型可以是( 几乎) 任何类型, 不仅仅是结构体类型: 任何类型都可以有方法, 甚至可以是函数类型, 可以是int、bool、string或数组的alias 类型。但是接收者不能是一个接口类型(参考 第11章) , 因为接口是一个抽象定义, 但是方法却是具体实现; 如果这样做会引发一个编译错误: *invalid receiver type…*
接收者类型可以是(几乎)任何类型,不仅仅是结构体类型:任何类型都可以有方法,甚至可以是函数类型,可以是 int、bool、string 或数组的别名 类型。但是接收者不能是一个接口类型(参考 第 11 章),因为接口是一个抽象定义,但是方法却是具体实现;如果这样做会引发一个编译错误:** invalid receiver type…**。
最后接收者不能是一个指针类型,但是它可以是任何其他允许类型的指针。
一个类型加上它的方法等价于OO 中的一个类。一个重要的区别是: 在Go中, 类型的代码和绑定在它上面的方法的代码可以不放置在一起, 它们可以存在在不同的源文件, 唯一的要求是: 它们必须是同一个包的。
一个类型加上它的方法等价于面向对象 中的一个类。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的。
类型T( 或*T) 上的所有方法的集合叫做类型T( 或*T) 的方法集。
类型 T( 或 \ *T) 上的所有方法的集合叫做类型 T( 或 \ *T) 的方法集。
因为方法是函数,所以同样的,不允许方法重载,即对于一个类型只能有一个给定名称的方法。但是如果基于接收者类型,是有重载的:具有同样名字的方法可以在 2 个或多个不同的接收者类型上存在,比如在同一个包里这么做是允许的:
@@ -19,25 +19,30 @@ func (a *denseMatrix) Add(b Matrix) Matrix
func ( a * sparseMatrix ) Add ( b Matrix ) Matrix
```
alias 类型不能有它原始类型上已经定义过的方法。
别名 类型不能有它原始类型上已经定义过的方法。
定义方法的一般格式如下:
```go
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 会自动解引用。
如果方法不需要使用recv的值, 可以用*_* 替换它,比如:
如果方法不需要使用 ` recv` 的值,可以用 **_** 替换它,比如:
```go
func ( _ receiver_type ) methodName ( parameter_list ) ( return_value_list ) { ... }
```
recv就像是OO 语言中的this或 self, 但是Go中并没有这两个关键字。随个人喜好, 你可以使用this或 self作为receiver的名字。下面是一个结构体上的简单方法的例子:
` recv` 就像是面向对象 语言中的 ` this` 或 ` self` ,但是 Go 中并没有这两个关键字。随个人喜好,你可以使用 ` this` 或 ` self` 作为 receiver 的名字。下面是一个结构体上的简单方法的例子:
示例 10.10 method .go:
Listing 10.10—method .go
```go
package main
@@ -77,7 +82,7 @@ func (tn *TwoInts) AddToParam(param int) int {
下面是非结构体类型上方法的例子:
Listing 10.11— method2.go:
示例 10.11 method2.go:
```go
package main
@@ -98,11 +103,11 @@ func main() {
}
```
练习 10.6: employee_salary.go
** 练习 10.6** employee_salary.go
定义结构体employee, 它有一个salary字段, 给这个结构体定义一个方法giveRaise来按照指定的百分比增加薪水。
定义结构体 ` employee` ,它有一个 ` salary` 字段,给这个结构体定义一个方法 ` giveRaise` 来按照指定的百分比增加薪水。
练习 10.7: iteration_list.go
** 练习 10.7** iteration_list.go
下面这段代码有什么错?
@@ -126,7 +131,7 @@ func main() {
cannot define new methods on non-local type int
比如想在time.Time上定义如下方法:
比如想在 ` time.Time` 上定义如下方法:
```go
func ( t time . Time ) first3Chars () string {
@@ -136,9 +141,9 @@ func (t time.Time) first3Chars() string {
类型在在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。
但是有一个绕点的方式: 可以先定义该类型( 比如int, float) 的别名类型, 然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。
但是有一个绕点的方式:可以先定义该类型(比如: int 或 float) 的别名类型, 然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。
Listing 10.12— method_on_time.go:
示例 10.12 method_on_time.go:
```go
package main
@@ -171,31 +176,31 @@ First 3 chars: Mon
## 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 中,( 接收者) 类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
*方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。*
* *方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。**
## 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 是指针,方法都支持运行了。
Listing 10.13— pointer_value.go:
示例 10.13 pointer_value.go:
```go
package main
@@ -228,9 +233,9 @@ func main() {
*/
```
试着在write()中改变接收者b的值: 将会看到它可以正常编译, 但是开始的b 没有被改变。
试着在 ` write()` 中改变接收者b的值: 将会看到它可以正常编译, 但是开始的 b 没有被改变。
我们知道方法不需要指针作为接收者, 如下面的例子, 我们只是需要Point3的值来做计算:
我们知道方法不需要指针作为接收者,如下面的例子,我们只是需要 ` Point3` 的值来做计算:
```go
type Point3 struct { x , y , z float }
@@ -240,25 +245,25 @@ func (p Point3) Abs float {
}
```
这样做稍微有点昂贵, 因为Point3是作为值传递给方法的, 因此传递的是它的拷贝, 这在Go中合法的。也可以在指向这个类型的指针上调用此方法( 会自动解引用) 。
这样做稍微有点昂贵,因为 ` Point3` 是作为值传递给方法的,因此传递的是它的拷贝,这在 Go 中合法的。也可以在指向这个类型的指针上调用此方法(会自动解引用)。
假设p3 定义为一个指针:* p3 := &Point{ 3, 4, 5}*
假设 `p3` 定义为一个指针:` p3 := &Point{ 3, 4, 5}` 。
可以这样写: * p3.Abs() 来替代 (*p3).Abs() *
可以使用 ` p3.Abs()` 来替代 ` (*p3).Abs()` 。
像例子10.11( method1.go) 中接收者类型是*TwoInts的方法 AddThem(), 它能在类型TwoInts的值上被调用, 这是自动间接发生的。
像例子 10.11( method1.go) 中接收者类型是 ` *TwoInts` 的方法 ` AddThem()` ,它能在类型 ` TwoInts` 的值上被调用,这是自动间接发生的。
因此two2.AddThem可以替代(&two2).AddThem()。
因此 ` two2.AddThem` 可以替代 ` (&two2).AddThem()` 。
在值和指针上调用方法:
可以有连接到类型的方法,也可以有连接到类型指针的方法。
* 但是这没关系: 对于类型T, 如果在*T上存在方法Meth(),并且t 是这个类型的变量, 那么t.Meth()会被自动转换为(&t).Meth().*
但是这没关系:对于类型 T, 如果在 \ *T 上存在方法 ` Meth()` ,并且 `t` 是这个类型的变量,那么 ` t.Meth()` 会被自动转换为 ` (&t).Meth()` 。
*指针方法和值方法都可以在指针或非指针上被调用*, 如下面程序所示, 类型List在值上有一个方法Len(), 在指针上有一个方法Append(),但是可以看到两个方法都可以在两种类型的变量上被调用。
* *指针方法和值方法都可以在指针或非指针上被调用** ,如下面程序所示,类型 ` List` 在值上有一个方法 ` Len()` ,在指针上有一个方法 ` Append()` ,但是可以看到两个方法都可以在两种类型的变量上被调用。
Listing 10.14— methodset1.go:
示例 10.14 methodset1.go:
```go
package main
@@ -287,11 +292,11 @@ func main() {
## 10.6.4 方法和未导出字段
考虑person2.go中的 person包: 类型Person被明确的导出了, 但是它的字段没有被导出。例如在use_person2.go中 p.firsetname就是错误的。该如何在另一个程序中修改或者只是读取一个Person的名字呢?
考虑 ` person2.go` 中的 ` person` 包:类型 ` Person` 被明确的导出了,但是它的字段没有被导出。例如在 ` use_person2.go` 中 ` p.firsetname` 就是错误的。该如何在另一个程序中修改或者只是读取一个 ` Person` 的名字呢?
这可以通过OO 语言一个众所周知的技术来完成: 提供getter和 setter方法。对于setter方法使用Set前缀, 对于getter方法只适用成员名。
这可以通过面向对象 语言一个众所周知的技术来完成:提供 getter 和 setter 方法。对于 setter 方法使用 Set 前缀,对于 getter 方法只适用成员名。
Listing 10.15— person2.go:
示例 10.15 person2.go:
```go
package person
@@ -310,7 +315,7 @@ func (p *Person) SetFirstName(newName string) {
}
```
Listing 10.16—use_person2.go:
示例 10.16—use_person2.go:
```go
package main
@@ -329,16 +334,16 @@ func main() {
fmt . Println ( p . FirstName ()) // Output: Eric
}
```
*并发访问对象:*
**并发访问对象**
对象的字段(属性)不应该由2个或2 个以上的不同线程在同一时间去改变。如果在程序发生这种情况, 为了安全并发访问, 可以使用包sync(参考9.3) 中的方法。在14.17我们会通过goroutines和 channels探索另一种方式。
对象的字段(属性)不应该由 2 个或 2 个以上的不同线程在同一时间去改变。如果在程序发生这种情况,为了安全并发访问,可以使用包 ` sync` (参考第 9.3 节) 中的方法。在第 14.17 节中 我们会通过 goroutines 和 channels 探索另一种方式。
## 10.6.5 内嵌类型的方法和继承
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌--- 在效果上等同于外层类型*继承*了这些方法:*将父类型放在子类型中来实现亚型*。这个机制提供了一种简单的方式来模拟经典OO 语言中的子类和继承相关的效果, 也类似Ruby中的混入( mixin) 。
当一个匿名类型被内嵌在结构体中时,匿名类型的可见方法也同样被内嵌,这 在效果上等同于外层类型 * *继承** 了这些方法:** 将父类型放在子类型中来实现亚型** 。这个机制提供了一种简单的方式来模拟经典面向对象 语言中的子类和继承相关的效果,也类似 Ruby 中的混入( mixin) 。
下面是一个示例(可以在练习 10.8中进一步学习) : 假定有一个Engine接口类型, 一个Car 结构体类型, 它包含一个Engine类型的匿名字段:
下面是一个示例(可以在练习 10.8 中进一步学习):假定有一个 ` Engine` 接口类型,一个 `Car` 结构体类型,它包含一个 ` Engine` 类型的匿名字段:
```go
type Engine interface {
@@ -363,7 +368,7 @@ func (c *Car) GoToWorkIn() {
}
```
下面是method3.go的完整例子, 它展示了内嵌结构体上的方法可以直接在外层类型的实例上调用:
下面是 ` method3.go` 的完整例子,它展示了内嵌结构体上的方法可以直接在外层类型的实例上调用:
```go
package main
@@ -394,7 +399,9 @@ func main() {
内嵌将一个已存在类型的字段和方法注入到了另一个类型里:匿名字段上的方法“晋升”成为了外层类型的方法。当然类型可以有只作用于本身实例而不作用于内嵌“父”类型上的方法,
可以覆写方法(像字段一样):和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法。在Listing 10.18—method4.go中添加:
可以覆写方法(像字段一样):和内嵌类型方法具有同样名字的外层类型的方法会覆写内嵌类型对应的方法。
在示例 10.18 method4.go 中添加:
```go
func ( n * NamedPoint ) Abs () float64 {
@@ -402,32 +409,33 @@ 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.8: inheritance_car.go
** 练习 10.8** inheritance_car.go
创建一个上面Car和 Engine可运行的例子, 并且给Car 类型一个wheelCount字段和一个numberOfWheels()方法。
创建一个上面 `Car` 和 ` Engine` 可运行的例子,并且给 `Car` 类型一个 ` wheelCount` 字段和一个 ` numberOfWheels()` 方法。
创建一个Mercedes类型, 它内嵌Car, 并新建Mercedes的一个实例, 然后调用它的方法。
创建一个 ` Mercedes` 类型,它内嵌 ` Car` ,并新建 ` Mercedes` 的一个实例,然后调用它的方法。
然后仅在Mercedes类型上创建方法sayHiToMerkel()并调用它。
然后仅在 ` Mercedes` 类型上创建方法 ` sayHiToMerkel()` 并调用它。
## 10.6.6 如何在类型中嵌入功能
主要有两种方法来实现在类型中嵌入功能:
A. 聚合(或组合):包含一个所需功能类型的具名字段
B. 内嵌: 内嵌( 匿名地) 所需功能类型, 像前一节10.6.5 所示的那样
A: 聚合(或组合):包含一个所需功能类型的具名字段。
B: 内嵌:内嵌(匿名地)所需功能类型,像前一节 10.6.5 所演 示的那样。
为了使这些概念具体化, 假设有一个Customer类型, 我们想让它通过Log 类型来包含日志功能, Log类型只是简单地包含一个累积的消息( 当然它可以是复杂的) 。如果想让特定类型都具备日志功能, 你可以实现一个这样的Log 类型, 然后将它作为特定类型的一个字段, 并提供Log(),它返回这个日志的引用。
为了使这些概念具体化,假设有一个 ` Customer` 类型,我们想让它通过 `Log` 类型来包含日志功能,` Log` 类型只是简单地包含一个累积的消息(当然它可以是复杂的)。如果想让特定类型都具备日志功能,你可以实现一个这样的 `Log` 类型,然后将它作为特定类型的一个字段,并提供 ` Log()` ,它返回这个日志的引用。
方式A 可以通过如下方法实现( 使用了10.7中的 String()功能):
方式 A 可以通过如下方法实现(使用了第 10.7 节中的 ` String()` 功能):
示例 10.19 embed_func1.go:
Listing 10.19—embed_func1.go:
```go
package main
@@ -520,7 +528,7 @@ func (c *Customer) String() string {
Log:{1 - Yes we can!
2 - After me the world will be a better place!}
内嵌的类型不需要指针, Customer也不需要Add方法, 它使用Log的Add 方法, Customer有自己的String方法, 并且在它里面调用了Log的 String方法。
内嵌的类型不需要指针,` Customer` 也不需要 `Add` 方法,它使用 `Log` 的 `Add` 方法,` Customer` 有自己的 ` String` 方法,并且在它里面调用了 `Log` 的 ` String` 方法。
如果内嵌类型嵌入了其他类型,也是可以的,那些类型的方法可以直接在外层类型中使用。
@@ -528,11 +536,12 @@ func (c *Customer) String() string {
## 10.6.7 多重继承
多重继承指的是类型获得多个父类型行为的能力, 它在传统的面向对象语言中通常是不被实现的( C++和 Python例外) 。因为在类继承层次中, 多重继承会给编译器引入额外的复杂度。但是Go语言中, 通过在类型中嵌入所有必要的父类型, 可以很简单的实现多重继承。
多重继承指的是类型获得多个父类型行为的能力, 它在传统的面向对象语言中通常是不被实现的( C++ 和 Python 例外)。因为在类继承层次中,多重继承会给编译器引入额外的复杂度。但是在 Go 语言中,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承。
作为一个例子, 假设有一个类型CameraPhone, 通过它可以Call(), 也可以TakeAPicture(), 但是第一个方法属于类型Phone, 第二个方法属于类型Camera。
作为一个例子,假设有一个类型 ` CameraPhone` ,通过它可以 ` Call()` ,也可以 ` TakeAPicture()` ,但是第一个方法属于类型 ` Phone` ,第二个方法属于类型 ` Camera` 。
只要嵌入这两个类型就可以解个问题,如下所示:
```go
package main
@@ -571,17 +580,17 @@ func main() {
It exhibits behavior of a Camera: Click
It works like a Phone too: Ring Ring
练习 10.9: point_methods.go:
** 练习 10.9** point_methods.go:
从point.go开始( 10.1的联系) : 使用方法来实现Abs()和 Scale()函数, Point作为方法的接收者类型。也为Point3和Polar实现Abs()方法。做 point.go中同样的事情, 只是这次通过方法。
从 ` point.go` 开始(第 10.1 节 的联系):使用方法来实现 ` Abs()` 和 ` Scale()` 函数,` Point` 作为方法的接收者类型。也为 ` Point3` 和 `Polar` 实现 `Abs()` 方法。完成了 ` point.go` 中同样的事情,只是这次通过方法。
练习 10.10: inherit_methods.go:
** 练习 10.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:
首先预测一下下面程序的结果,然后动手实验下:
@@ -620,21 +629,21 @@ func main() {
## 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 章),标准库广泛的应用了这些规则,在标准库中这些通用方法都有一致的名字,比如 ` Open()` 、` Read()` 、` Write()` 等。想写规范的 Go 程序,就应该遵守这些约定,给方法合适的名字和签名,就像那些通用方法那样。这样做会使 Go 开发的软件更加具有一致性和可读性。比如:如果需要一个 convert-to-string 方法,应该命名为 ` String()` ,而不是 ` ToString()` (参考第 10.7 节)。
## 10.6.9 和其他面向对象语言比较 Go 的类型和方法
在如 C++、Java、C# 和 Ruby 这样的面向对象语言中,方法在类的上下文中被定义和继承:在一个对象上调用方法时,运行时会检测类以及它的超类中是否有此方法的定义,如果没有会导致异常发生。
在Go 中, 这样的继承层次是完全没必要的: 如果方法在此类型定义了, 就可以调用它, 和其他类型上是否存在这个方法没有关系。在这个意义上, Go具有更大的灵活性。
在 Go 语言 中, 这样的继承层次是完全没必要的: 如果方法在此类型定义了, 就可以调用它, 和其他类型上是否存在这个方法没有关系。在这个意义上, Go 具有更大的灵活性。
下面的模式就很好的说明了这个问题:

Go不需要一个显式的类定义, 如同Java、C++、C#等那样,相反地,, “类”是通过提供一组作用于一个共同类型的方法集来隐式定义的。类型可以是结构体或者任何用户自定义类型。
Go 不需要一个显式的类定义,如同 Java、C++、C# 等那样,相反地,“类”是通过提供一组作用于一个共同类型的方法集来隐式定义的。类型可以是结构体或者任何用户自定义类型。
比如: 我们想定义自己的Integer类型, 并添加一些类似转换成字符串的方法, 在Go中可以如下定义:
比如:我们想定义自己的 ` Integer` 类型,并添加一些类似转换成字符串的方法,在 Go 中可以如下定义:
```go
type Integer int
@@ -643,35 +652,36 @@ func (i *Integer) String() string {
}
```
在Java或 C#中, 这个方法需要和类Integer的定义放在一起, 在Ruby中可以直接在基本类型int上定义这个方法。
在 Java 或 C# 中,这个方法需要和类 ` Integer` 的定义放在一起,在 Ruby 中可以直接在基本类型 int 上定义这个方法。
**总结: **
**总结**
在Go中, 类型就是类( 数据和关联的方法) 。Go不知道类似OO 语言的类继承的概念。继承有两个好处:代码复用和多态。
在 Go 中, 类型就是类( 数据和关联的方法) 。Go 不知道类似面向对象 语言的类继承的概念。继承有两个好处:代码复用和多态。
在Go中, 代码复用通过组合和委托实现, 多态通过接口的使用来实现: 有时这也叫*组件编程*。
在 Go 中,代码复用通过组合和委托实现,多态通过接口的使用来实现:有时这也叫 * *组件编程** 。
许多开发者说相比于类继承, Go 的接口提供了更强大、却更简单的多态行为。
**备注**:
**备注**
如果真的需要更多OO 的能力,看一下goop 包( Go Object-Oriented Programming) , 它来自与 Scott Pakin[(https://github.com/losalamos/goop] : 它给Go提供了JavaScript风格的对象( 基于原型的对象) , 并且支持多重继承和类型独立分派, 通过它可以实现你喜欢的其他编程语言里的一些结构。
如果真的需要更多面向对象 的能力,看一下 [`goop` ](https://github.com/losalamos/goop ) 包( Go Object-Oriented Programming) , 它由 Scott Pakin 编写 : 它给 Go 提供了 JavaScript 风格的对象(基于原型的对象),并且支持多重继承和类型独立分派,通过它可以实现你喜欢的其他编程语言里的一些结构。
问题 10.1:
** 问题 10.1**
我们在某个类型的变量上使用点号调用一个方法: variable.method(), 在使用Go以前, 在哪儿碰到过OO 的点号?
我们在某个类型的变量上使用点号调用一个方法:` variable.method()` ,在使用 Go 以前,在哪儿碰到过面向对象 的点号?
问题 10.2:
** 问题 10.2**
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 { ... }` 。
d) 对于新定义的Integer, 和b) 中同样的问题
d) 对于新定义的 ` Integer` ,和 b) 中同样的问题。
## 链接
- [目录 ](directory.md )
- 上一节:[10.5 匿名字段和内嵌结构体 ](10.5.md )
- 下一节:[10.7 TODO ](10.7.md )
- 上一节:[匿名字段和内嵌结构体 ](10.5.md )
- 下一节:[类型的 String() 方法和格式化描述符 ](10.7.md )