微信号:Golangweb

介绍:欢迎来到 Go语言社区 社区网址:www.golangweb.com APP网址:app.golangweb.com 社区非公司性质,完全个人爱好建立;做的不好的地方大家见谅.

Go语言面向对象编程

2016-07-29 22:26 Golang语言社区

前段时间接触Go语言,感觉有很多新的理念,今天先转载一篇文章,以后有时间再慢慢学习。

本文转载自图灵社区http://www.ituring.com.cn/article/details/1339 作者许式伟


面向对象编程

Go 语言的面向对象编程(OOP)非常简洁而优雅。说它简洁,简介之处在于,它没有了OOP中很多概念,比如:继承、虚函数、构造函数和析构函数、隐藏的this指针等等。说它优雅,是它的面向对象(OOP)是语言类型系统(type system)中的天然的一部分。整个类型系统通过接口(interface)串联,浑然一体。

类型系统(type system)

很少有编程类的书籍谈及类型系统(type system)这个话题。但实际上类型系统是整个语言的支撑,至关重要。

类型系统(type system)是指一个语言的类型体系图。在整个类型体系图中,包含这些内容:

 基本类型。如byte、int、bool、float等等。
 复合类型。如数组(array)、结构体(struct)、指针(pointer)等。
 Any类型。即可以指向任意对象的类型。
 值语义和引用语义。
 面向对象。即所有具备面向对象特征(比如有成员方法)的类型。
 接口(interface)。

类型系统(type system)描述的是这些内容在一个语言中如何被关联。比如我们聊聊Java的类型系统: 在Java语言中,存在两套完全独立的类型系统,一套是值类型系统,主要是基本类型,如byte、int、boolean、char、double、String等,这些类型基于值语义。一套是以Object类型为根的对象类型系统,这些类型可以定义成员变量、成员方法、可以有虚函数。这些类型基于引用语义,只允许new出来(只允许在堆上)。只有对象类型系统中的实例可以被Any类型引用。Any类型就是整个对象类型系统的根 —— Object类型。值类型想要被Any类型引用,需要装箱(Boxing)过程,比如int类型需要装箱成为Integer类型。只有对象类型系统中的类型才可以实现接口(方法是让该类型从要实现的接口继承)。

在Go语言中,多数类型都是值语义,并且都可以有方法。在需要的时候,你可以给任何类型(包括内置类型)“增加”新方法。实现某个接口(interface)无需从该接口继承(事实上Go语言并没有继承语法),而只需要实现该接口要求的所有方法。任何类型都可以被Any类型引用。Any类型就是空接口,亦即 interface{}。 让我们一一道来。

给类型增加方法

在Go语言中,你可以给任意类型(包括内置类型,但指针类型除外)增加方法,例如:

type Integer intfunc (a Integer) Less(b Integer) bool {    return a < b}

在这个例子中,我们定义了一个新类型Integer,它和int没有本质不同,只是它为内置的int类型增加了个新方法:Less。如此,你就可以让整型看起来像个类那样用:

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

在学其他语言的时候,很多初学者对面向对象感到很神秘。我在给初学者介绍面向对象的时候,经常说到“面向对象只是一个语法糖”。以上代码用面向过程的方式来写是这样的:

type Integer intfunc Integer_Less(a Integer, b Integer) bool {return a < b}func main() {var a Integer = 1if Integer_Less(a, 2) {    fmt.Println(a, "Less 2")}}

在Go语言中,面向对象的神秘面纱被剥得一干二净。对比这两段代码:

    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)  // 面向过程

你可以看出,面向对象只是换了一种语法形式来表达。在Go语言中没有隐藏的this指针。这句话的含义是:

第一,方法施加的目标(也就是“对象”)显式传递,没有被隐藏起来。
第二,方法施加的目标(也就是“对象”)不需要非得是指针,也不用非得叫this。

我们对比Java语言的代码:

class Integer {    private int val;    public boolean Less(Integer b) {        return this.val < b.val;    }}

这段Java代码初学者会比较难懂,主要是因为Integer类的Less方法隐藏了第一个参数Integer* this。如果将其翻译成C代码,会更清晰:

struct Integer {    int val;};bool Integer_Less(Integer* this, Integer* b) {    return this->val < b->val;}

在Go语言中的面向对象最为直观,也无需支付额外的成本。如果要求对象必须以指针传递,这有时会是个额外成本,因为对象有时很小(比如4个字节),用指针传递并不划算。

只有在你需要修改对象的时候,才必须用指针。它不是Go语言的约束,而是一种自然约束。举个例子:

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

这里为Integer类型增加了Add方法。由于Add方法需要修改对象的值,所以需要用指针引用。调用如下:

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

运行该程序得到的结果是:a = 3。如果你不用指针:

func (a Integer) Add(b Integer) {    a += b}

运行程序得到的结果是:a = 1,也就是维持原来的值。究其原因,是因为Go和C语言一样,类型都是基于值传递。要想修改变量的值,只能传递指针。

值语义和引用语义

值语义和引用语义的差别在于赋值:

b = a b.Modify()

如果b的修改不会影响a的值,那么此类型属于值类型。如果会影响a的值,那么此类型是引用类型。

多数Go语言中的类型,包括:

 基本类型。如byte、int、bool、float32、float64、string等等。
 复合类型。如数组(array)、结构体(struct)、指针(pointer)等。

都基于值语义。Go语言中类型的值语义表现得非常彻底。我们这么说是因为数组(array)。如果你学习过C语言,你会知道C语言中的数组(array)比较特别。通过函数传递一个数组的时候基于引用语义,但是在结构体中定义数组变量的时候是值语义(表现在结构体赋值的时候,该数组会被完整地拷贝一份新的副本)。

Go语言中的数组(array)和基本类型没有区别,是很纯粹的值类型。例如:

var a = [3]int{1, 2, 3}var b = a b[1]++fmt.Println(a, b)

程序运行结果:[1 2 3] [1 3 3]。这表明b = a赋值语句是数组内容的完整拷贝。要想表达引用,需要用指针:

var a = [3]int{1, 2, 3}var b = &a b[1]++fmt.Println(a, *b)

程序运行结果:[1 3 3] [1 3 3]。这表明b=&a赋值语句是数组内容的引用。变量b的类型不是[3]int,而是*[3]int类型。

Go语言中有4个类型比较特别,看起来像引用类型:

 切片(slice):指向数组(array)的一个区间。
 字典(map):极其常见的数据结构,提供key-value查询能力。
 通道(chan):执行体(goroutine)间通讯设施。
 接口(interface):对一组满足某个契约的类型的抽象。

但是这并不影响我们将Go语言类型是值语义的本质。我们一个个来看这些类型:

切片(slice)本质上是range,你可以大致将 []T 表示为: