第6章 方法
从90年代开始,面向对象编程(OOP)就成了称霸工程界和教育界的编程范式,所以之后几乎所有大规模被应用的语言都包含了对OOP的支持,Go语言也不例外
关于OOP,其实没有明确定义,但是大概的意思是,一个对象其实也就是一个简单的值或者一个变量,在这个对象中会包含一些方法,而一个方法则是一个一个和特殊类型相关联的函数,一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对象的用户就不需要直接去操作对象,而是借助方法来做这些事情
在早些章节,其实我们已经使用过标准库提供的一些方法,比如time.Duration这个类型的Seconds方法
const day = 24*time.Hour
fmt.Println(day.Seconds())//86400
在2.5节中,我们定义了一个方法,Celsius类型的String方法
func (c Celsius) String() string{
return fmt.Sprintf("%g˚C",c)
}
在本章中,OOP编程的第一方面,我们会展示如何有效的定义和使用方法,我们会覆盖到OOP编程的两个关键点,封装和组合
6.4 方法值和方法表达式
我们经常选择一个方法,并在同一个表达式里执行,比如常见的p.Distacne()形式,实际上将其分成两部分执行也行的。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))
scalP := p.ScaleBy
scalP(2)
scalP(3)
scalP(10)
在一个包的API需要一个函数值、且调用方希望操作的是某一个绑定了对象的方法的话,“方法值”非常有用,例如time.Func这个函数的功能是在指定到延迟时间之后来执行一个函数,且这个函数操作的是一个Rocket对象r
type Rocket struct{/* ... */}
func (r *Rocket)lunch(){
r := new(Rocket)
time.After(10*time.Second,func(){r.lunch()})
直接用方法“值”传入AfterFunc的话可以更简短
time.AfterFunc(10*time.Second,r.Launh)
注意:省略掉了上面的匿名函数
和方法“值”相关的还有方法表达式:与调用一个普通的函数相比,调用方法时,我们必须要选用选择器(p.Distance)语法来指定方法的接收器
当T是一个类型时,方法表达式可能会写成T.f或者(*T).f,会返回一个函数“值”,这种函数会将其第一个参数用作接收器,所以可以用不写选择器的方式来对其进行调用
p:=Point{1,2}
q:=Point{4,6}
distance := Point.Distance
fmt.Println(distance(q,p))//5 好像变回了调用函数一样
fmt.Printf("%T\n",distance)//func(main.Point, main.Point) float64
scale := (*Point).ScaleBy
scale(&p,2)
fmt.Println(p) // {2 4}
fmt.Printf("%T\n",scale) //func(*main.Point, float64)
当你根据一个变量来决定调用同一个类型的哪个函数时,方法表达式就显得很有用了,你可以根绝选择来调用接收器各不相同的方法,在下例中,变量op代表Point类型的Add或者Sub方法,Path.TranslateBy方法会为其Path数组中的每一个Point来调用对应的方法
type Point struct {X,Y float64}
func (p Point) Add(q Point) Point{return Point{p.X+q.X,p.Y+q.Y}}
func (p Point) Sub(q Point) Point{return Point{p.X-q.X,p.Y-q.Y}}
type Path []Point
func (path Path) TranslateBy (offset Point,add bool) {
var op func(p,q Point) Point
if add{
op = Point.Add
}else {
op = Point.Sub
}
for i := range path{
path[i] = op(path[i],offset)
}
}