1. Code
  2. Coding Fundamentals

与Go同行:Golang面向对象编程

Scroll to top

Chinese (Simplified) (中文(简体)) translation by Qiang Ji (you can also view the original English article)

Go是一个新想法和旧想法的奇特混合。 它有一个非常让人耳目一新的方法,就是它不怕丢弃已有的概念“如何做事情”。 很多人甚至都不确定Go是否是一个面向对象的语言。 让我马上给它做个最终结论吧。 Go是一个面向对象的语言。

在本教程中你将学到在Go语言中所有面向对象设计的复杂性,面向对象编程的柱石像封装,继承和多态性在Go语言中是如何被表达的,Go语言是如何跟其它语言比较的。

Go语言设计哲学

Go的根是基于C语言和更广泛的Algol语言家族。 Ken Thompson半开玩笑地说过,Rob Pike,Robert Granger和他自己聚在一起,决定他们讨厌C++语言。 不管这是不是一个笑话,Go语言和C++语言非常的不同。 稍后我们再多聊点这个。 Go语言是关于终极的简单性。 这在Rob Pike的少即是多中被详细地解释过。

Go语言 vs. 其它语言

Go语言没有类,没有对象,没有异常和模板。 它支持垃圾回收和内建的并发。 有关面向对象方面的最显著的遗漏是Go语言中没有类型层次结构。 这与大多数面向对象语言如C++,Java,C#,Scala甚至动态语言(如Python和Ruby)形成对比。

Go面向对象语言特性

Go语言没有类,但它支持类型。 特别是, 它支持structs。 Structs是用户定义的类型。 Struct类型(含方法)提供类似于其它语言中类的服务。

Structs

一个struct定义一个状态。 这里有一个Creature struct。 它有一个Name属性和一个布尔类型的标志Real,告诉我们它是一个真实的Creature还是一个虚构的Creature。 Structs只保存状态,不保存行为。

1
type Creature struct {
2
3
  Name string
4
5
  Real bool
6
7
}
8

方法

方法是对特定类型进行操作的函数。 它们有一个接收器条款,命令它们对什么样的类型可进行操作。 这里是一个Dump()方法,它可对Creature结构进行操作,并打印出它们的状态:

1
func (c Creature) Dump() {
2
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
3
}

这是一个不太常见的语法,但是它非常的具体和清晰(不像隐喻的"this"和Python语言中令人困惑的"self")。

嵌入

你可以将匿名的类型嵌入进彼此。 如果你嵌入一个无名的struct那么被嵌入的struct对接受嵌入的struct直接提供它自己的状态(和方法)。 比如,FlyingCreature有一个无名子的被嵌入的Creature struct,这意味着一个FlyingCreature就是一个Creature

1
type FlyingCreature struct {
2
  Creature
3
  WingSpan int
4
}

现在,如果你有一个FlyingCreature的实例,你可以直接访问它的Name和Real属性。

1
dragon := &FlyingCreature{
2
    Creature{"Dragon", false, },
3
    15,
4
}
5
6
fmt.Println(dragon.Name)
7
fmt.Println(dragon.Real)
8
fmt.Println(dragon.WingSpan)

接口

接口是Go语言对面向对象支持的标志。 接口是声明方法集的类型。 与其它语言中的接口类似,它们不包含方法的实现。

实现所有接口方法的对象自动地实现接口。 它没有继承或子类或“implements”关键字。 在下面的代码中,Foo类型实现了Fooer接口(按照惯例,Go接口名称以“er”结尾)。

1
type Fooer interface {
2
  Foo1()
3
  Foo2()
4
  Foo3()
5
}
6
7
type Foo struct {
8
}
9
10
func (f Foo) Foo1() {
11
    fmt.Println("Foo1() here")
12
}
13
14
func (f Foo) Foo2() {
15
    fmt.Println("Foo2() here")
16
}
17
18
func (f Foo) Foo3() {
19
    fmt.Println("Foo3() here")
20
}

面对对象的设计:Go的方式

让我们看看Go语言如何衡量面向对象编程的支柱:封装,继承和多态性。 这些是基于类的编程语言的特征,它们是最流行的面向对象的编程语言。

在其核心,对象是语言的构造,它们有状态和行为,这些行为对状态进行操作并且选择性地把它们曝露给程序的其它部分。

封装

Go语言在包的级别进行封装。 以小写字母开头的名称只在该程序包中可见。 你可以隐藏私有包中的任何内容,只暴露特定的类型,接口和工厂函数。

例如,在这里要隐藏上面的Foo类型,只暴露接口,你可以将其重命名为小写的foo,并提供一个NewFoo()函数,返回公共Fooer接口:

1
type foo struct {
2
}
3
4
func (f foo) Foo1() {
5
    fmt.Println("Foo1() here")
6
}
7
8
func (f foo) Foo2() {
9
    fmt.Println("Foo2() here")
10
}
11
12
func (f foo) Foo3() {
13
    fmt.Println("Foo3() here")
14
}
15
16
func NewFoo() Fooer {
17
    return &Foo{}
18
}

然后来自另一个包的代码可以使用NewFoo()并访问由内部foo类型实现的Footer接口:

1
f := NewFoo()
2
3
f.Foo1()
4
5
f.Foo2()
6
7
f.Foo3()

继承

继承或子类化始终是一个有争议的问题。 实现继承有许多问题(与接口继承相反)。 C++和Python以及其它语言实现的多重继承受着致命的死亡钻石问题,但即使是单一继承也会有面对脆弱的基类问题的处境。

现代语言和面向对象的思维现在倾向于组合而不是继承。 Go语言对此很严肃,它没有任何类型层次结构。 它允许你通过组合来共享实现的细节。 但Go语言,在一个非常奇怪的转变中(这可能源于实用的考量),允许嵌入匿名组合。

通过嵌入一个匿名类型的组合等同于实现继承,这是它所有意图和目的。 一个嵌入的struct与基类一样脆弱。 你还可以嵌入一个接口,这相当于在Java或C ++等语言中继承一个接口。 如果嵌入类型没有实现所有接口方法,它甚至可能导致产生在编译时未被发现的运行错误。

这里SuperFoo嵌入Fooer接口,但是SuperFoo没有实现Foo的方法。 Go编译器会愉快地让你创建一个新的SuperFood并调用Fooer的方法,但很显然这在运行时会失败。 这会编译:

1
type SuperFooer struct {
2
  Fooer
3
}
4
5
func main() {
6
  s := SuperFooer{}
7
  s.Foo2()

运行此程序会导致一个panic:

1
panic: runtime error: invalid memory address or nil pointer dereference
2
[signal 0xb code=0x1 addr=0x28 pc=0x2a78]
3
4
goroutine 1 [running]:
5
panic(0xde180, 0xc82000a0d0)
6
  /usr/local/Cellar/go/1.6/libexec/src/runtime/panic.go:464 +0x3e6
7
main.main()
8
  /Users/gigi/Documents/dev/go/src/github.com/oop_test/main.go:104 +0x48
9
exit status 2
10
11
Process finished with exit code 1

多态性

多态性是面向对象编程的本质:只要对象坚持实现同样的接口,Go语言就能处理不同类型的那些对象。 Go接口以非常直接和直观的方式提供这种能力。

这里有一个精心准备的例子,实现Dumper接口的多个creatures(和一个door!)被创建并存储在一个slice中,然后针对其中每一个调用Dump()方法。 你会注意到不同实例化对象的风格。

1
package main
2
3
import "fmt"
4
5
type Creature struct {
6
  Name string
7
  Real bool
8
}
9
10
func Dump(c*Creature) {
11
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
12
}
13
14
func (c Creature) Dump() {
15
  fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
16
}
17
18
type FlyingCreature struct {
19
  Creature
20
  WingSpan int
21
}
22
23
func (fc FlyingCreature) Dump() {
24
  fmt.Printf("Name: '%s', Real: %t, WingSpan: %d\n",
25
    fc.Name,
26
    fc.Real,
27
    fc.WingSpan)
28
}
29
30
type Unicorn struct {
31
  Creature
32
}
33
34
type Dragon struct {
35
  FlyingCreature
36
}
37
38
type Pterodactyl struct {
39
  FlyingCreature
40
}
41
42
func NewPterodactyl(wingSpan int) *Pterodactyl {
43
  pet := &Pterodactyl{
44
    FlyingCreature{
45
      Creature{
46
        "Pterodactyl",
47
        true,
48
      },
49
      wingSpan,
50
    },
51
  }
52
  return pet
53
}
54
55
type Dumper interface {
56
  Dump()
57
}
58
59
type Door struct {
60
  Thickness int
61
  Color     string
62
}
63
64
func (d Door) Dump() {
65
  fmt.Printf("Door => Thickness: %d, Color: %s", d.Thickness, d.Color)
66
}
67
68
func main() {
69
  creature := &Creature{
70
    "some creature",
71
    false,
72
  }
73
74
  uni := Unicorn{
75
    Creature{
76
      "Unicorn",
77
      false,
78
    },
79
  }
80
81
  pet1 := &Pterodactyl{
82
    FlyingCreature{
83
      Creature{
84
        "Pterodactyl",
85
        true,
86
      },
87
      5,
88
    },
89
  }
90
91
  pet2 := NewPterodactyl(8)
92
93
  door := &Door{3, "red"}
94
95
  Dump(creature)
96
  creature.Dump()
97
  uni.Dump()
98
  pet1.Dump()
99
  pet2.Dump()
100
101
  creatures := []Creature{
102
    *creature,
103
    uni.Creature,
104
    pet1.Creature,
105
    pet2.Creature}
106
  fmt.Println("Dump() through Creature embedded type")
107
  for _, creature := range creatures {
108
    creature.Dump()
109
  }
110
111
  dumpers := []Dumper{creature, uni, pet1, pet2, door}
112
  fmt.Println("Dump() through Dumper interface")
113
  for _, dumper := range dumpers {
114
    dumper.Dump()
115
  }
116
}

结论

Go是一个真正的面向对象的编程语言。 它支持基于对象的建模,并鼓励使用接口而不是具体类型层次结构的最佳实践。 Go做了一些不寻常的语法选择,但总体来说,在使用类型,方法和界面方面感觉简单,轻量和自然。

Go语言的嵌入特性做的不是很纯粹,但很明显这很实用,通过提供嵌入而不是仅仅用名字来实现组合。