这篇文章写的真心是好, 把Golang的OOP诠释的很清楚.
本imi增加了, 自己的理解.
struct有内嵌(组合), 有重写, 但是没有重载; interface具有多态性.

说道面向对象(OOP)编程, 就不得不提到下面几个概念:
- 抽象
- 封装
- 继承(重写, 重载)
- 多态

其实有个问题Is Go An Object Oriented Language?, 随便谷歌了一下, 你就发现讨论这个的文章有很多:
1. reddit
2. google group

那么问题来了

  1. Golang是OOP吗?
  2. 使用Golang如何实现OOP?

我入门教程基本就是A Tour Of Go以及Go Web 编程. 由于之前是写C++, 但是说到Go面向对象编程, 总是感觉怪怪的, 总感觉缺少点什么. 我搜集了一些资料和例子, 加上我的一些理解, 整理出这样一篇文章.

一. 抽象和封装

抽象和封装就放在一块说了. 这个其实挺简单. 看一个例子就行了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type rect struct {
    width int
    height int
}
 
func (r *rect) area() int {
    return r.width * r.height
}
 
func main() {
    r := rect{width: 10, height: 5}
    fmt.Println("area: ", r.area())
}
 

完整代码

要说明的几个地方:
1、Golang中的struct和其他语言的class是一样的.

2、可见性. 这个遵循Go语法的大小写的特性

3、上面例子中, 称*rectreceiver. 关于receiver 可以有两种方式的写法:

1
2
3
4
func (r *rect) area() int {
    return r.width * r.height
}
 

1
2
3
4
func (r rect) area() int {
    return r.width * r.height
}
 

这其中有什么区别和联系呢? 关于详细解释请查看astaxie的解释, 写的非常清晰.

简单来说, Receiver可以是值传递, 还是可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。

4、当Receiver*rect指针的时候, 使用的是r.width, 而不是(*r).width, 是由于Go自动帮我转了,两种方式都是正确的.

5、任何类型都可以声明成新的类型, 因为任何类型都可以有方法.

1
2
3
4
5
type Interger int
func (i Interger) Add(interger Interger) Interger {
    return i + interger
}
 

6、虽然Interger是从int声明而来, 但是这样用是错误的.

1
2
3
var i Interger = 1
var a int = b //cannot use i (type Interger) as type int in assignment
 

这是因为Go中没有隐式转换(写C++的同学都会特别讨厌这个, 因为编译器背着我们干的事情太多了). Golang中类型之间的相互赋值都必须显式声明.

上面的例子改成下面的方式就可以了.

1
2
3
var i Interger = 1
var a int = int(b)
 

二. 继承(Composition)

说道继承,其实在Golang中是没有继承(Extend)这个概念. 因为Golang舍弃掉了像C++, Java的这种传统的、类型驱动的子类。

Go Effictive says:
Go does not provide the typical, type-driven notion of subclassing, but it does have the ability to “borrow” pieces of an implementation by embedding types within a struct or interface.

换句话说, Golang中没有继承, 只有Composition.

Golang中的Compostion有两种形式, 匿名组合(Pseudo is-a)非匿名组合(has-a)

注: 如果不了解OOP的is-ahas-a关系的话, 请自行google.

1. has-a

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package main
 
import (
    "fmt"
)
 
type Human struct {
    name  string
    age   int
    phone string
}
 
type Student struct {
    h      Human //非匿名字段
    school string
}
 
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
 
func (s *Student) SayHi() {
    fmt.Printf("Hi student, I am %s you can call me on %s", s.h.name, s.h.phone)
}
 
func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    fmt.Println(mark.h.name, mark.h.age, mark.h.phone, mark.school)
    mark.h.SayHi()
    mark.SayHi()
 
}
 

Output

Mark 25 222-222-YYYY MIT
Hi, I am Mark you can call me on 222-222-YYYY
Hi student, I am Mark you can call me on 222-222-YYYY

完整代码

这种组合方式, 其实对于了解传统OOP的话, 很好理解, 就是把一个struct作为另一个struct的字段.

从上面例子可以, Human完全作为Student的一个字段使用. 所以也就谈不上继承的相关问题了.我们也不去重点讨论.

2. is-a(Pseudo)----Embedding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Human struct {
    name string
    age int
    phone string
}
 
type Student struct {
    Human //匿名字段
    school string
}
 
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"}
    fmt.Println(mark.name, mark.age, mark.phone, mark.school)
    mark.SayHi()
}
 

Output

Mark 25 222-222-YYYY MIT
Hi, I am Mark you can call me on 222-222-YYYY

完整代码

这里要说的有几点:

1、字段
现在Student访问Human的字符, 就可以直接访问了, 感觉就是在访问自己的属性一样. 这样就实现了OOP的继承.

1
2
fmt.Println("Student age:", mark.age) //输出: Student age: 25
 

但是, 我们也可以间接访问:

1
2
fmt.Println("Student age:", mark.Human.age) //输出: Student age: 25
 

这有个问题, 如果在Student也有个字段name, 那么当使用mark.name会以Studentname为准.

1
2
fmt.Println("Student name:", mark.name) //输出:Student Name: student name
 

完整代码

2、方法
Student也继承了HumanSayHi()方法

1
2
mark.SayHi() // 输出: Hi, I am Mark you can call me on 222-222-YYYY
 

当然, 我们也可以重写SayHi()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type Human struct {
    name  string
    age   int
    phone string
}
 
type Student struct {
    Human  //匿名字段
    school string
    name   string
}
 
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
 
func (h *Student) SayHi() {
    fmt.Println("Student Sayhi")
}
 
func main() {
    mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT", "student name"}  
    mark.SayHi()
}