面对对象
method
现在假设有这么一个场景,你定义了一个 struct 叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现
type Rectangle struct {
width, height float64
}
func area(r Rectangle) float64 {
return r.width * r.height
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
fmt.Println("Area of r1 is: ", area(r1))
fmt.Println("Area of r2 is: ", area(r2))
}
Area of r1 is: 24
Area of r2 is: 36
1. 这段代码可以计算出来长方形的面积,但是 area () 不是作为 Rectangle 的方法实现的(类似面向对象里面的方法)
2. 而是将 Rectangle 的对象(如 r1, r2)作为参数传入函数计算面积的。
3. 这样实现当然没有问题咯,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,你想计算他们的面积的时候怎么办啊?
4. 那就只能增加新的函数咯,但是函数名你就必须要跟着换了
5. 变成 area_rectangle, area_circle, area_triangle...
func area(r Rectangle) float64 {
return r.width * r.height
}
func area_rectangle(r Rectangle) float64 {
return xx
}
很显然,这样的实现并不优雅,并且从概念上来说 "面积" 是 "形状" 的一个属性,它是属于这个特定的形状的,就像长方形的长和宽一样。
基于上面的原因所以就有了 method 的概念
1. method 是附属在一个给定的类型上的,他的语法和函数的声明语法几乎一样
2. 只是在 func 后面增加了一个 receiver (也就是 method 所依从的主体)。
method 的语法如下:
type Rectangle struct {
width, height float64
}
// 虽然Rectangle结果没有定义method_area方法, 但是这种方式就可以
func (rec Rectangle) method_area(long float64) float64 {
return rec.width * rec.height * long
}
func main() {
r1 := Rectangle{12, 2}
r2 := Rectangle{9, 4}
// 调用method_area方法
fmt.Println(r1.method_area(1))
fmt.Println(r2.method_area(2))
}
24
72
在使用 method 的时候重要注意几点
1. 虽然 method 的名字一模一样,但是如果接收者不一样,那么 method 就不一样
2. method 里面可以访问接收者的字段
3. 调用 method 通过 . 访问,就像 struct 里面访问字段一样
那是不是 method 只能作用在 struct 上面呢?
1. 当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct 等各种类型上面。
2. 什么叫自定义类型,自定义类型不就是 struct 嘛,不是这样的哦,struct 只是自定义类型里面一种比较特殊的类型而已
3. 还有其他自定义类型申明,可以通过如下这样的申明来实现
type ages int
type money float32
type months map[string]int
func main() {
m := months{
"January": 31,
"February": 28,
"December": 31,
}
fmt.Println(m)
}
map[December:31 February:28 January:31]
可以在任何的自定义类型中定义任意多的 method
type Color byte
func (c Color) String() string {
strings := []string{"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
return strings[c]
}
func main() {
col := Color(2)
fmt.Println(col.String())
}
BLUE
指针作为 receiver
可以把 receiver 当作 method 的第一个参数来看,然后结合前面函数讲解的传值和传引用就不难理解
type Color struct {
index int
}
func (c *Color) Str() string {
strings := []string{"WHITE", "BLACK", "BLUE", "RED"}
// 对, 你没看错, 指针可以直接选择属性,当然这样也可以(*c).index
return strings[c.index]
}
func main() {
col := Color{2}
// Go 知道 receiver 是指针,他自动帮你转了, 这样也可以(&col).Str()
fmt.Println(col.Str())
}
BLUE
method 继承
我们学习了字段的继承,那么你也会发现 Go 的一个神奇之处,method 也是可以继承的。
如果匿名字段实现了一个 method,那么包含这个匿名字段的 struct 也能调用该 method。
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"}
// 没错, 继承了Human的结构体都可以调用这个方法
mark.SayHi()
sam.SayHi()
}
Hi, I am Mark you can call me on 222-222-YYYY
Hi, I am Sam you can call me on 111-888-XXXX
method 重写
如果 Employee 想要实现自己的 SayHi, 怎么办?简单,和匿名字段冲突一样的道理,我们可以在 Employee 上面定义一个 method,重写了匿名字段的方法。
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()
}
Hi, I am Mark you can call me on 222-222-YYYY
Hi, I am Sam, I work at Golang Inc. Call me on 111-888-XXXX
Go 里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现 (大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。