IS GO OBJECT-ORIENTED?

类和继承

struct, not class

类(class)在面向对象编程中是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的特性和方法。

GO语言中并没有传统面向对象编程语言中的类class的概念,取而代之的是使用了结构体struct。但是GO语言的结构体可比C语言中的同名前辈要强大得多。在GO语言中,结构体以及结构体的方法method发挥了与传统意义上的”类”相同的作用,同时在概念上又清晰分明——结构体只负责管理状态,不管理行为;而结构体的方法则负责定义结构体的行为,比如说允许他们更改状态.

继承与结构和组合

继承是面向对象软件技术当中的一个概念,如果一个类别 B “继承自”另一个类别 A,就把这个 B 称为 “A的子类”,而把 A 称为 “B的父类别” 也可以称 “A 是 B 的超类”。

继承有如下两个特性:

  • 子类具有父类别的各种属性和方法,不需要再次编写相同的代码。

  • 子类别继承父类时,可以重新定义某些属性,并重写某些方法,使其获得与父类别不同的功能。

GO语言中没有继承,这一点在GO语言官方文档的FAQ也有明确说明:

Object-oriented programming, at least in the best-known languages, involves too much discussion of the relationships between types, relationships that often could be derived automatically. Go takes a different approach.

程序设计理念中最广为认知的准则之一即是 用组合代替继承。这一准则在四人帮的那本极富盛名的《设计模式》也屡有提及。而在GO语言中,这一准则被发挥得淋漓尽致。

当我们在定义一个结构体时,我们可以追加类型为另一个结构体的匿名字段。这样一来,我们定义的这个结构体也就同时拥有了另一个结构体的所有字段以及方法。这种技法被称之为Struct Embedding

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
package main

import (
"fmt"
)

type Dog struct {
Animal
}
type Animal struct {
Age int
}

func (a *Animal) Move() {
fmt.Println("Animal moved")
}
func (a *Animal) SayAge() {
fmt.Printf("Animal age: %d\n", a.Age)
}
func main() {
d := Dog{}
d.Age = 3
d.Move()
d.SayAge()
}

可以在 main()中能够看到,Dog实例是可以使用和调用Animal实例的一些公开属性和方法的。在简单的使用效果上会与继承有些接近。

接口

忘记Java或PHP风格的接口概念吧!GO语言中的接口是截然不同的,而其中最关键的一个特性即是: 接口是隐式实现的——不需要在定义类型时去显式声明一下类型实现了哪些接口。

摘抄自GO语言官方文档的FAQ:

Rather than requiring the programmer to declare ahead of time that two types are related, in Go a type automatically satisfies any interface that specifies a subset of its methods.

通常,接口的定义都很短,甚至有可能至包含一个方法声明。在地道的GO语言实践中,你不应该看到一个接口拥有长长的方法列表。借助这样的接口就可以很优雅地实现多态: 一个方法如果接受某一个接口,那么就意味着这个方法接受了任何实现了该接口的对象.

方法

GO语言中的类型往往都拥有方法,但是这些方法的定义是独立于类型定义而存在的。GO语言在语法层面通过一个类似Javascript中prototype方法的特性来实现了这种定义的独立性。

1
2
3
4
5
6
7
8
9
function Person(first, last) {
this.firstName = first;
this.lastName = last;
}
Person.prototype.name = function() {
return this.firstName + " " + this.lastName;
};
p = new Person("Flavio", "Copes")
p.name() // Flavio Copes

在GO语言中,代码应该写成这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import (
"fmt"
)
type Person struct {
firstName string
lastName string
}
func (p Person) name() string {
return p.firstName + " " + p.lastName
}
func main() {
p := Person{"Flavio", "Copes"}
fmt.Println(p.name())
}

关联方法与类型

方法可以被关联至任何类型,甚至是”曲线救国”式地关联到GO语言中的基础类型。由于方法只能被关联至在同一个包中定义的类型,所以我们无法直接”扩展”基础类型。但事实上,我们可以通过对基础数据类型定义别名从而达到扩展的目的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
)

type Amount int

func (a *Amount) Add(add Amount) {
*a += add
}

func main() {
var a Amount
a = 1
a.Add(2)
fmt.Println(a)
}

函数

回想一下传统的面向对象编程语言,比如Java。想想看曾经定义过多少次包含的全是static方法的名为”Utils”的类?

这样的现象来源与传统的面向对象变成语言中的一个观念: 一切皆是对象,因此函数定义必须放在一个类里面。所幸的是,这种现象不会发生在GO语言中,因为GO语言有真正的”函数”这一概念。在真实世界中,并非所有事物都必须是一个对象。”类”和”对象”的概念很有用,但也不能到处都用。

在GO语言中,并非所有东西都是对象(若严格从技术角度而言,GO语言没有东西是对象。但通常人们会将一个类型的实例或者变量称之为”对象”),方法仅仅是指那些被关联至某一类型的函数。但GO语言同时又允许函数脱离于对象而独立存在,就如同C语言的函数一样。

所以,GO语言既允许方法存在,也允许函数存在。而且,函数是第一优先的(函数类型可用作定义结构体字段,函数可作为参数传递至其他函数,函数也可作为返回值被函数或方法返回)。

大道至简

综上所述,GO语言对于面向对象的实现非常灵活且直接。不用再纠结于类和继承,你可以减少对源码模板文件的依赖,而且不用再一点一点地推敲类与类之间的理想层级结构,从此你只需根据需求自由地对类型进行组合或拆解即可。

IS GO OBJECT ORIENTED

Frequently Asked Questions中也表述过下述内容

Is Go an object-oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous—but not identical—to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes). Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.