一个面向对象的程序会用方法来表达其属性和对应的操作,使用这个对象的用户就不需要直接去操作对象,而是借助方法来完成这些事情。

OOP编程的关键点:封装和组合。

方法声明

1
2
3
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}

函数名称之前的附加参数会将函数附加到这种类型上,即为该类型定义了一个独占方法。附加的参数p,是方法的接收器。

Go中可以给任意类型定义方法,所以可以给字符串,数值等定义一些附加行为。方法可以被声明到任意类型,只要不是一个指针或者一个interface。

为一个类型定义一个方法,比定义一个函数来的简便,因为从包外去调函数要写的代码要多点。。

基于指针对象的方法

调用一个函数的时候,会对其每一个参数值进行拷贝。所以Go语言中是按值传参的,但是对于切片,map这种是引用类型的,即使按值来传,它也是传的引用,而引用可以等同于指针。

对应于方法的接收器来说,如果接受者变量本身比较大的时候,就可以用指针来声明方法。

1
2
3
4
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}

一般实践中,只要这个类型有一个指针作为接收器的方法,其他的方法都应该使用指针接收器。如果类型本身是一个指针的话,是不允许出现在接收器中的。

在调用的时候,即使用该类型变量来调用使用指针作为接收器的方法时候,也是可以的,因为编译器会隐式的去取变量的地址,然后用指针调用这个方法。

而且如果接收器不是指针,那么通过指针调用方法,编译器会默认的解引用,然后去调用方法。

nil也是合法的接收器类型

如果nil是对象来说是合法的零值的时候,比如map或者slice。

IntList是一个结构体。

1
2
3
4
5
6
func (list *IntList) Sum() int {
if list == nil {
return 0
}
return list.Value + list.Tail.Sum()
}

通过嵌入结构体来扩展类型

内嵌字段会指导编译器去生成额外的包装方法来委托已经声明好的方法。

1
2
3
4
5
6
func (p ColoredPoint) Distance(q Point) float64 {
return p.Point.Distance(q)
}
func (p *ColoredPoint) ScaleBy(factor float64) {
p.Point.ScaleBy(factor)
}

下面这种实现就是利用了嵌入结构体的方法访问机制

1
2
3
4
5
6
7
8
9
10
11
12
var cache = struct {
sync.Mutex
mapping map[string]string
}{
mapping: make(map[string]string),
}
func Lookup(key string) string {
cache.Lock()
v := cache.mapping[key]
cache.Unlock()
return v
}

方法值和方法表达式

1
2
distanceFromP := p.Distance
fmt.Println(distanceFromP(q))

方法值就是一个绑定对象的函数。

方法表达式,调用方法的时候,需要用选择器来指定方法的接收器。T是一个类型的时候,方法表达式可能会写作T.f或者(*T).f,这样会返回一个函数值,这种函数会将其第一个参数用作接收器,因此可以用普通函数的形式调用它。

1
2
distance := Point.Distance
fmt.Println(distance(p, q))

哎函数是第一类公民嘛,说了这么多。。。

封装

一个对象的变量或者方法如果对调用方是不可见的话,就定义为封装。