go语言圣经第六章(读书笔记)
时间:2019-07-24 19:38:33
收藏:0
阅读:98
第六章 方法
- “尽管没有被大众所接受的明确的OOP的定义,从我们的理解来讲,一个对象其实也就是一个
简单的值或者一个变量,在这个对象中会包含一些方法,而一个方法则是一个一个和特殊类
型关联的函数。一个面向对象的程序会用方法来表达其属性和对应的操作,这样使用这个对
象的用户就不需要直接去操作对象,而是借助方法来做这些事情。”
方法声明
- 在函数声明时,在其名字之前放上一个变量,就是这个变量(或其类型)的方法。
type Point struct{ X, Y float64 }
// traditional function
func Distance(p, q Point) float64 {
return math.Hypot(q.X-p.X, q.Y-p.Y)
}
// 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 := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(p, q)) // "5", function call
fmt.Println(p.Distance(q))
// "5", method call
- 这个附加的变量(上例中的p),称为方法的接收器(receiver)
- 这个附加的变量,相当于其它oop定义的this或者self
- 像p.Distance的表达式叫做选择器(点操作符,还可以访问struct类型里的字段)
- 方法可以被声明到任何类型,只要不是一个指针或者一个interface
基于指针对象的方法
- 当变量本身比较大时,通常采用其对应指针作为接收器
func (p *Point) ScaleBy(factor float64) {
p.X *= factor
p.Y *= factor
}
- “在现实的程序里,一般会约定如果Point这个类有一个指针作为接收器的方法,那么所有Point的方法都必须有一个指针接收器,即使是那些并不需要这个指针接收器的函数”
- 译者的标注:
1.不管你的method的receiver是指针类型还是非指针类型,都是可以通过指针/非指针类型进行调用的,编译器会帮你做类型转换
2.在声明一个method的receiver该是指针还是非指针类型时,你需要考虑两方面的内部,第一方面是这个对象本身是不是特别大,如果声明为非指针变量时,调用会产生一次拷贝;第二方面是如果你用指针类型作为receiver,那么你一定要注意,这种指针类型指向的始终是一块内存地址,就算你对其进行了拷贝。熟悉C或者C艹的人这里应该很快能明白。 - 补充一点:使用指针类型作为接收器,如果该方法修改了接收器变量的值,那是真的修改了;使用非指针类型作为接收器,该方法使用的只是其拷贝,修改了值对原来的变量并没有影响
Nil也是一个合法的接收器类型
// An IntList is a linked list of integers.
// A nil *IntList represents the empty list.
type IntList struct {
Value int
Tail
*IntList
}
// Sum returns the sum of the list elements.
func (list *IntList) Sum() int {
if list == nil {
return 0
}
return list.Value + list.Tail.Sum()
}
通过嵌入结构体来扩展类型
- 当嵌入类型为非指针类型
package main
import (
"fmt"
)
type Point struct{ X, Y float64 }
func (p *Point) ins() {
p.X++
p.Y++
}
type ColoredPoint struct {
Point
Color string
}
func main() {
cp := &ColoredPoint{
Point: Point{X: 5, Y:6},
Color: "red",
}
cp.ins()
fmt.Println(cp)
}
- 当嵌入类型是指针类型
package main
import (
"fmt"
)
type Point struct{ X, Y float64 }
func (p *Point) ins() {
p.X++
p.Y++
}
type ColoredPoint struct {
*Point
Color string
}
func main() {
cp := &ColoredPoint{
&Point{X: 5, Y:6},
"red",
}
cp.ins()
fmt.Println(cp)
fmt.Println(cp.Point)
fmt.Println(cp.X)
fmt.Println(cp.Y)
fmt.Println(cp.Color)
}
方法值
- 用函数值和方法值作类比会很容易理解
1.通过选择器p.Distance获取方法值,存入变量中
2.通过方法值变量调用该方法(就像换了个名字一样)
p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance // method value
fmt.Println(distanceFromP(q)) // "5"
var origin Point // {0, 0}
fmt.Println(distanceFromP(origin)) // "2.23606797749979", sqrt(5)
scaleP := p.ScaleBy // method value
scaleP(2) // p becomes (2, 4)
scaleP(3) // then (6, 12)
scaleP(10) // then (60, 120)
- 通过方法值的形式省去匿名函数的编写
type Rocket struct { /* ... */ }
func (r *Rocket) Launch() { /* ... */ }
r := new(Rocket)
time.AfterFunc(10 * time.Second, func() { r.Launch() })
//上下等价
time.AfterFunc(10 * time.Second, r.Launch)
封装
- 通过大小写实现
- 封装的优点:
1.首先,因为调用方不能直接修改对象的变量值,其只需要关注少量的语句并且只要弄懂少量变量的可能的值即可。
2.隐藏实现的细节,可以防止调用方依赖那些可能变化的具体实现,这样使设计包的程序员在不破坏对外的api情况下能得到更大的自由。
3.阻止了外部调用方对对象内部的值任意地进行修
改
原文:https://www.cnblogs.com/laiyuanjing/p/11240216.html
评论(0)