Go 面向对象

本文将介绍 Go 中的结构体、方法、接口、面向对象支持。

一、结构体

1. 结构体定义

1
2
3
4
5
type 结构体名 struct {
成员名1 类型1
成员名2 类型2
···
}

需要注意的是:

  • 成员名可以省略,当省略时,成员将 “嵌入”

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    type Person struct {
    Name string
    Phone string
    Addr string
    }

    type Book struct {
    Title string
    Person
    ... ...
    }

    // 类型名即是成员名
    book.Person.Name

    // 也可以省略成员名
    book.Name

2. 结构体实例化

1
2
3
4
5
6
7
8
9
10
11
// 创建值变量,自动赋零值
var a 结构体名

// 创建值变量,赋值
var a = 结构体名{ 成员名1: 值1, 成员名2: 值2, ...}

// 创建指针变量,自动赋零值
var a = new(结构体名)

// 创建指针变量,自动赋零值 【第二种写法】
var a = &结构体名{}

3. 结构体成员访问

1
结构体.成员名

4. 构造函数

Go 语言并不支持直接在结构体中设置构造函数,通常我们会在同一个包下设置函数 NewXxx(),以约定俗成的方式作为构造函数。

如果希望强制使用构造函数,可以将类型变为私有。

1
2
3
4
5
6
7
type goods struct {
name string
}

func NewGoods(name string) *goods {
return &goods{name}
}

二、方法

1. 什么是方法?

方法与函数的区别在于,函数可以直接调用,而方法需要 “借助” 调用者调用。

2. 方法的定义

在 Go 语言中,只需要在函数之上额外设置接受者,函数便成为方法。

1
2
3
func (接受者 接受者类型) 函数名([参数列表]) [返回值列表] {
// 函数体
}

3. 方法接收者

接受者应该是命名类型的值或指针。

命名类型:通过类型定义所定义出来的类型

选择接收者类型时应该注意:

  • 如果方法中的改动要反映到接收者之上,应该选择指针类型
  • 如果接受者较大,考虑到值拷贝的开销,也可以选择指针类型

4. 方法值

1
2
3
var 方法值 = 接受者.方法名

方法值()

5. 方法表达式

1
2
3
var 方法表达式 = 接受者类型.方法名

方法表达式(接受者[, 参数1, ...])

三、接口

1. 什么是接口?

  • 接口是一系列方法的集合
  • 接口是隐式实现的,程序员不需要手动指定接口的实现与否,只要一个类型定义了接口中包含的所有方法,它就自动实现了该接口
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
type Phone interface {
call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
// ...
}

type Iphone struct {
}

func (iphone Iphone) call() {
// ...
}

func main() {
var phone Phone

phone = new(NokiaPhone)
phone.call()

phone = new(Iphone)
phone.call()
}

2. 空接口

空接口的方法集合为空,这意味着任何类型都是对空接口的实现。因此,类型为空接口的形参可以接受任何值。

包括 Go 标准库在内的一些通用方法的实现,都使用了空类型作为数据元素类型。

3. 类型断言

一个接口可以被多个类型实现,接口类型的变量的具体类型是不确定的,可能是任何类型的。

因此,Go 提供了类型断言,类型断言会尝试将接口类型的变量转换为指定类型(或接口)。

1
2
3
4
5
var i interface{} = 7
var a, ok = i.(int)
if !ok {
// ...
}

4. 反射

在 Go 中,反射可以在运行时访问变量的类型、值、方法等。

通过 reflect.TypeOf() 可以获取到任意值的类型对象,类型对象是 reflect.Type 类的实例,可以通过它获取到类型信息。

1
2
3
var a int
typeOfA := reflect.TypeOf(a)
fmt.Println(typeOfA.Name(), typeOfA.Kind())

通过 reflect.ValueOf() 可以获取到任意值的值对象,值对象是 reflect.Value 类的实例,可以通过它获取到值信息。

1
2
3
var a int
valueOf := reflect.ValueOf(a)
fmt.Println(valueOf.Int(), valueOf.Kind())

四、嵌入

1. 类型嵌入结构体

可以在定义结构体时嵌入类型,从而实现属性和方法的复用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type Person struct {
Name string
Phone string
Addr string
}

type Book struct {
Title string
Person
...
}

// 类型名即是成员名
book.Person.Name

// 也可以省略成员名
book.Name

2. 接口嵌入结构体

可以在定义接口体时嵌入接口,从而实现方法定义的复用。需要注意的是,接口体拥有该方法的定义,但是如果没有具体实现,调用时也会报错。

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
type I interface {
M1()
M2()
}

type T struct {
I
}

func (T) M1() {
fmt.Println("M1")
}

func (T) M3() {
fmt.Println("M3")
}

func main() {
var t T

// M1
t.M1()

// panic: runtime error: invalid memory address or nil pointer dereference
t.M2()

// M3
t.M3()
}

3. 接口嵌入接口

可以在一个接口的定义中嵌入另一个接口,该接口中将拥有被嵌入接口的所有方法,从而实现接口定义的复用。

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
type Reader interface {
Read(p []byte) (n int, err error)
}

type Writer interface {
Write(p []byte) (n int, err error)
}

type Closer interface {
Close() error
}

type ReadWriter interface {
Reader
Writer
}

type ReadCloser interface {
Reader
Closer
}

type WriteCloser interface {
Writer
Closer
}

type ReadWriteCloser interface {
Reader
Writer
Closer
}

五、面向对象支持

面向对象有三个基本特征,Go 对它们的支持情况如下:

  • 封装:在 Go 语言中,首字母大写的 “内容” 在包外可见,首字母小写的 “内容” 在包外不可见,可以借助这一特性实现封装
  • 继承:Go 中并没有继承这一概念,但是可以通过组合(即内嵌)的方法实现能力的继承
  • 多态:通过接口可以实现多态

参考