设为首页 加入收藏

TOP

基于类型系统的面向对象编程语言Go(一)
2015-04-07 15:30:07 来源: 作者: 【 】 浏览:90
Tags:基于 类型 系统 面向 对象 编程语言

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


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


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


类型系统(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语言中,你可以给任意类型(包括内置类型,但指针类型除外)增加方法,例如:


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


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


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


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


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


我们对比Java语言的代码:


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


因为切片(slice)内部是一系列的指针,所以可以改变所指向的数组(array)的元素并不奇怪。slice类型本身的赋值仍然是值语义。


字典(map)本质上是一个字典指针,你可以大致将map[K]V表示为:


基于指针(pointer)???我们完全可以自定义一个引用类型,如:


通道(chan)和字典(map)类似,本质上是一个指针。为什么将他们设计为是引用类型而不是统一的值类型,是因为完整拷贝一个通道(chan)或字典(map)不是常规需求。


同样,接口(interface)具备引用语义,是因为内部维持了两个指针。示意为:


接口在Go语言中的地位非常重要。关于接口(interface)内部实现细节,后面在高阶话题中,我们再细细剖析。


Go语言的结构体(struct)和其它语言的类(class)有同等的地位。但Go语言放弃了包括继承在内的大量OOP特性,只保留了组合(compose)这个最基础的特性。


组合(compose)甚至不能算OOP的特性。因为连C语言这样的过程式编程语言中,也有结构体(struct),也有组合(compose)。组合只是形成复合类型的基础。


上面我们说到,所有的Go语言的类型(指针类型除外)都是可以有自己的方法。在这个背景下,Go语言的结构体(struct)它只是很普通的复合类型,平淡无奇。例如我们要定义一个矩形类型:


然后我们定义方法Area来计算矩形的面积:


初始化


定义了Rect类型后,我们如何创建并初始化Rect类型的对象实例?有如下方法:


在Go语言中,未显式进行初始化的变量,都会初始化为该类型的零值(例如对于bool类型的零值为false,对于int类型零值为0,对于string类型零值为空字符串)。


构造函数?不需要。在Go语言中你只需要定义一个普通的函数,只是通常以NewXXX来命名,表示“构造函数”:


func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}


这一切非常自然,没有任何突兀之处。


确切地说,Go语言也提供了继承,但是采用了组合的文法,我们称之为匿名组合:


以上代码定义了一个Base类(实现了Foo、Bar两个成员方法),然后定义了一个Foo类,从 Base“继承”并实现了改写了Bar方法,该方法实现时先调用了基类的Bar方法。


在“派生类”Foo没有改写“基类”Base的成员方法时,相应的方法就被“继承”。例如在上面的例子中,调用foo.Foo() 和调用foo.Base.Fo

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Java 不使用工具包把json更快的转.. 下一篇12条自问让你更好地编程

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: