方法声明
在函数声明时,在其名字之前放上一个变量,即是一个方法。这个附加的参数会将该函数附加到这种类型上,即相当于为这种类型定义了一个独占的方法。
// same thing, but as a method of the Point type
func (p Point) Distance(q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
附加的参数p,叫做方法的接收器(receiver)。
基于指针对象的方法
这个接受者变量本身比较大时,我们就可以用其指针而不是对象来声明方法
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
这个方法的名字是(Point).ScaleBy
只有类型(Point)和指向他们的指针(Point),才是可能会出现在接收器声明里的两种接收器。此外,为 了避免歧义,在声明方法时,如果一个类型名本身是一个指针的话,是不允许其出现在接收器中的
type P *int
func (P) f() { /* ... */ } // compile error: invalid receiver type
在每一个合法的方法调用表达式中,不论是接收器的实际参数和其接收器的形式参数相同,比如两者都是类型T或者都是类型*T:
Point{1, 2}.Distance(q) // Point
pptr.ScaleBy(2) // *Point
或者接收器形参是类型T,但接收器实参是类型*T,这种情况下编译器会隐式地为我们取变量的地址:
p.ScaleBy(2) // implicit (&p)
或者接收器形参是类型*T,实参是类型T。编译器会隐式地为我们解引用,取到指针指向的实际变量:
pptr.Distance(q) // implicit (*pptr)
如果类型T的所有方法都是用T类型自己来做接收器(而不是*T),那么拷贝这种类型的实例就是安全的;
通过嵌入结构体来扩展类型
内嵌可以使我们定义字段特别多的复杂类型,我们可以将字段先按小类型分组,然后定义小类型的方法,之后再把它们组合起来。
方法值和方法表达式
我们经常选择一个方法,并且在同一个表达式里执行,比如常见的p.Distance()形式,实际上将其分成 两步来执行也是可能的。p.Distance叫作“选择器”,选择器会返回一个方法"值"->一个将方法 (Point.Distance)绑定到特定接收器变量的函数。这个函数可以不通过指定其接收器即可被调用;即调 用时不需要指定接收器(译注:因为已经在前文中指定过了),只要传入函数的参数即可:
p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance
fmt.Println(distanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin))
scaleP := p.ScaleBy // method value
}
scaleP(2)
scaleP(3)
scaleP(10)
封装
一个对象的变量或者方法如果对调用方是不可见的话,一般就被定义为“封装”。封装有时候也被叫做信息隐藏,同时也是面向对象编程最关键的一个方面。
Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不 会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。因而如果我们想要封装一个对 象,我们必须将其定义为一个struct。
封装的三方面有点
- 因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并
且只要弄懂少量变量的可能的值即可。 - 隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破 坏对外的api情况下能得到更大的自由。
- 阻止了外部调用方对对象内部的值任意地进行修改
总结
- 方法是特殊的函数
- 方法的接受者可以是指针,但是指针类型的数据,不能作为接受者
- 方法值可以在使用方法时,更简短实用
- 封装的使用需要结合struct,更好的发挥面向对象的好处
网友评论