Go面向对象详解
今天我们来了解一下 Go
中函数的另一种形态,带有接收者的函数,我们称为 method
。
method
是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样,只是在 func
后面增加了一个 receiver
(也就是 method
所依从的主体)。
用 Rob Pike 的话来说就是:
"A method is a function with an implicit first argument, called a receiver."
method 的语法如下:
func (r ReceiverType) funcName(parameters) (results)
例如下面的 method
来实现面积的求和:
package main
import (
"fmt"
)
type Rectangle struct {
width, height float64
}
// 这样就实现了method
func (r Rectangle) area() float64 {
return r.width * r.height
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
fmt.Println("Area of r1 is: ", r1.area())
fmt.Println("Area of r2 is: ", r2.area())
}
使用 method
有下面的几个特点:
- 虽然
method
的名字一模一样,但是如果接收者不一样,那么method
就不一样。 method
里面可以访问接收者的字段。- 调用
method
通过 . 访问,就像struct
里面访问字段一样。
Receiver
还可以是指针,两者的差别在于,指针作为 Receiver
会对实例对象的内容发生操作,而普通类型作为 Receiver
仅仅是以副本作为操作对象,并不对原实例对象发生操作。
后文对此会有详细论述。
method
只能作用在 struct
上面吗?
当然不是,他可以定义在任何你自定义的类型、内置类型、struct 等各种类型上面。
struct
只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。
type typeName typeLiteral
请看下面这个申明自定义类型的代码。
type ages int
type money float32
type months map[string]int
实际上只是一个定义了一个别名,有点类似于 c
中的 typedef
,例如上面 ages
替代了 int
。
指针作为 receiver
package main
import "fmt"
type Tree struct {
Value int
Left *Tree
Righ *Tree
}
func (t Tree) setValue1(v int) {
t.Value = v
}
func (t *Tree) setValue2(v int) {
t.Value = v
}
func main() {
t1 := Tree{
Value: 0,
Left: nil,
Righ: nil,
}
t1.setValue1(1)
fmt.Println(t1) // {0 <nil> <nil>} 并没有修改原来的值
t1.setValue2(2)
fmt.Println(t1) // {2 <nil> <nil>} 对原来的Tree修改了
t2 := &Tree{
Value: 0,
Left: nil,
Righ: nil,
}
t2.setValue1(1)
fmt.Println(t2) // &{0 <nil> <nil>} // 并没有对原来的值进行修改
t2.setValue2(2)
fmt.Println(t2) // &{2 <nil> <nil>}
}
从上面的例子可以看出来:
-
如果一个
method
的receiver
是*T
, 你可以在一个T
类型的实例变量V
上面调用这个method
,而不需要&V
去调用这个method
。 -
如果一个
method
的receiver
是T
,你可以在一个T
类型的变量P
上面调用这个method
,而不需要*P
去调用这个method
这种特性:让开发者不必关注,调用的指针的 method
还是不是指针的 method
,Go 能推断出你的目的,这对于有多年 C/C++
编程经验的同学来说,真是解决了一个很大的痛苦。
如果你的方法想修改值就将 receiver
改为指针类型,否则就使用实例变量类型。
method 继承
如果匿名字段实现了一个 method
,那么包含这个匿名字段的 struct
也能调用该 method
。
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human // 匿名字段
school string
}
type Employee struct {
Human // 匿名字段
company string
}
// 在 human 上面定义了一个 method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
method 重写
上面的例子中,如果 Employee
想要实现自己的 SayHi
, 怎么办?
简单,和匿名字段冲突一样的道理,我们可以在 Employee
上面定义一个 method
,重写了匿名字段的方法。
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human // 匿名字段
school string
}
type Employee struct {
Human // 匿名字段
company string
}
// Human 定义 method
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
// Employee 的 method 重写 Human 的 method
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
func main() {
mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
mark.SayHi()
sam.SayHi()
}
上面的代码设计的是如此的美妙,让人不自觉的为 Go
的设计惊叹!
通过这些内容,我们可以设计出基本的面向对象的程序了。
但是 Go
里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现 (大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。