理解go语言编程-面向对象编程

在Go语言中,你可以给任意类型(包括内置类型,但不包括指针类型)添加相应的方法,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}

func main() {
var a Integer = 1
if a.Less(2) {
fmt.Println(a, "Less 2")
}
}

func (a Integer) Less(b Integer) bool { // 面向对象 return a < b
}
func Integer_Less(a Integer, b Integer) bool { // 面向过程 return a < b
}
a.Less(2) // 面向对象的用法
Integer_Less(a, 2) // 面向过程的用法

C++语言的面向对象之所以让有些人迷惑的一大原因就在于其隐藏的this指针。一旦把隐藏的this指针显露出来,大家看到的就是一个面向过程编程。如果读者了解Python语法,就会知道Python的成员方法中会有一个self参数,它和this指针的作用是完全一样的。

1
2
3
4
5
struct Integer { int val;
};
bool Integer_Less(Integer* this, Integer* b) { // 不隐藏的写法
return this->val < b->val;
}

go传递指针而不是值的写法:

1
2
3
func (a *Integer) Add(b Integer) {
*a += b
}

C语言中的数组比较特别。通过函数传递一个数组的时候基于引用语义,但是在结构体中定义数组变量的时候基于值语义(表现在为结构体赋值的时候,该数组会被完整地复制)。Go语言中的数组和基本类型没有区别,是很纯粹的值类型

1
2
3
4
5
6
7
8
9
var a = [3]int{1, 2, 3} var b = a
b[1]++
fmt.Println(a, b)
// [1 2 3] [1 3 3]
// 要想表达引用,需要用指针
var a = [3]int{1, 2, 3} var b = &a
b[1]++
fmt.Println(a, *b)
// [1 3 3] [1 3 3]

类的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
type Rect struct {
x, y float64
width, height float64
}

func NewRect(x, y, width, height float64) *Rect { // 类似构造函数
return &Rect{x, y, width, height}
}

rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

Go语言也提供了继承,但是采用了组合的文法,所以我们将其称为匿名组合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Base struct {
Name string
}
func (base *Base) Foo() { ... }
func (base *Base) Bar() { ... }

type Foo struct {
Base
...
}
func (foo *Foo) Bar() {
foo.Base.Bar() // 从Base类“继承”并改写了Bar()方法(该方法实现时先调用了基类的Bar() 方法)
...
}
// 内存布局发生了改变。“基类”Base 的数据放在了“派生类”Foo的最后
type Foo struct {
... // 其他成员
Base
}
// 还可以以指针方式从一个类型“派生”,在C++ 语言中其实也有类似的功能,那就是虚基类
type Foo struct {
*Base
...
}

Go语言中符号的可访问性是包一级的而不是类型一级的。如果Go语言符号的可访问性是类型一级的,少不了还要加上friend这样的关键字,以表示两个类是朋友关系,可以访问彼此的私有成员。

在Go语言中,一个类只需要实现了接口要求的所有函数,我们就说这个类实现了该接口。

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
type File struct {
// ...
}
func (f *File) Read(buf []byte) (n int, err error)
func (f *File) Write(buf []byte) (n int, err error)
func (f *File) Seek(off int64, whence int) (pos int64, err error) func (f *File) Close() error

type IFile interface {
Read(buf []byte) (n int, err error)
Write(buf []byte) (n int, err error)
Seek(off int64, whence int) (pos int64, err error) Close() error
}
type IReader interface {
Read(buf []byte) (n int, err error)
}
type IWriter interface {
Write(buf []byte) (n int, err error)
}
type ICloser interface {
Close() error
}
// 尽管File类并没有从这些接口继承,甚至可以不知道这些接口的存在,但是File类实现了这些接口,可以进行赋值
var file1 IFile = new(File)
var file2 IReader = new(File)
var file3 IWriter = new(File)
var file4 ICloser = new(File)

Go语言可以根据下面的函数: func (a Integer) Less(b Integer) bool
自动生成一个新的Less()方法,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a += b
}

type LessAdder interface {
Less(b Integer)
bool Add(b Integer)
}
// 以下函数可以自动推导生成
func (a *Integer) Less(b Integer) bool {
return (*a).Less(b)
}
// 但这个不能生成,
func (a Integer) Add(b Integer) {
(&a).Add(b) // 改变的只是函数参数a,这不符合用户的预期
}

var a Integer = 1
var b LessAdder = &a // 编译通过
var b LessAdder = a // 编译失败

在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么它们就是等同的,可以相互赋值。接口赋值并不要求两个接口必须等价。如果接口A的方法列表是接口B的方法列表的子集, 么接口B可以赋值给接口A。

检查file1接口指向的对象实例是否实现了two.IStream接口,如果实现了,则执行特定的代码。接口查询是否成功,要在运行期才能够确定。它不像接口赋值,编译器只需要通过静态类型检查即可判断赋值是否可行。

1
2
3
4
var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
...
}

在Go语言中,你可以询问接口它指向的对象是否是某个类型,比如

1
2
3
4
var file1 Writer = ...
if file6, ok := file1.(*File); ok {
...
}

接口组合,可以认为接口组合是类型匿名组合的一个特定场景,只不过接口只包含方法,而不包含任何成员变量。

1
2
3
4
5
6
7
8
9
10
// ReadWriter接口将基本的Read和Write方法组合起来
type ReadWriter interface {
Reader
Writer
}
// 它完全等同于如下写法
type ReadWriter interface {
Read(p []byte) (n int, err error)
Write(p []byte) (n int, err error)
}

nephen wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
坚持原创技术分享,您的支持将鼓励我继续创作!