Go语言学习笔记2:面向对象编程
前言
相比于传统的Java,C#和C++语言相比,Go语言对于面向对象的支持非常的简洁而优雅。简洁之处在于,Go语言并没有沿袭传统面向对象的诸多的概念,如继承(采用组合进行替换)、虚函数、构造函数和析构函数、隐藏的this指针等。优雅之处在于,Go语言对面向对象编程的支持是语言类型系统中的天然组成部分。整个类型系统通过接口串联,浑然一体。
类型系统
类型系统是指一个语言的类型体系结构,一个典型的类型系统包括如下的基本内容:基础类型(byte、int、bool、float等),复合类型(数组、结构体/类、指针等),指向任意对象的类型(Object/Any等),值语义和引用语义,面向对象的特征、接口等。
Java语言将类型系统分成两类,基本类型系统(byte、int、long、double、boolean等8种)和引用类型(继承至Object类)。相比于Java语言,Go语言中的大多数类型都是值语义,并且都可以包含对应的操作方法。在需要的时候,你可以给任何类型(包括内置类型)“增加”新方法。而在实现某个接口时,无需从该接口继承(Go不支持继承),只需要实现该接口要求的所有方法即可。任何类型都可以被Any类型引用。Any类型就是空接口,即interface{}。
为类型系统添加新方法
在Go语言中,你可以给任意类型(包括内置类型,但是不包括指针类型)添加相应的方法,如下所示:
1.png
Go语言的面向对象最为直观,无需支付额外的成本。如果要求对象必须以指针传递,这有时会是个额外的成本,因为对象有时候很小(如几个字节),用指针传递并不划算。只有你需要进行修改的时候,才需要用指针,如Integer类型的加法运算 func (a *Integer )Add (b Integer){ return *a = * a + b}。注意:Go语言和C语言一样,类型都是基于值传递的。要想修改变量的值,只能传递指针。
值语义和引用语义
值类型对象传递时候会完整的拷贝一份数据,引用类型对象则不会。Go语言中大多数类型都是值语义,包括基本类型(byte、int、bool等)和复合类型(数组、结构体、指针)等。
2.png
结构体
Go语言的结构体(struct)和其他语言的类(class)有同等的地位,但是Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合这个最基础的特性。组合甚至不能算是面向对象的特性,因为在C语言中这样的过程编程语言中,也有结构体和组合。组合只是形成复合类型的基础。
初始化
定义一个Rect结构体之后,应该如何进行初始化呢,如下所示:
3.png
在Go语言中,未进行显示初始化的变量都会被初始化为该类型的零值,例如bool类型的零值为false,int类型的零值为0,string类型的零值为空字符串。在Go语言中没有构造函数的概念,对象的创建通常交给一个全局的创建函数来完成,以NewXX来命名,表示“构造函数”
匿名组合
准确的来说,Go语言也提供了继承,但是采用的是组合的方式,所以我们将其称为匿名组合。
可见性
Go语言的关键字很少,其中没有类似Java语言中的private、public、、protected这样的关键字。要使某个符号对其他包(package)可见,需要将该符号定义为大写开头,结构体的成员方法的可达性遵循同样的规则。
接口
Go语言的主要设计者之一Rob Pike曾说过,如果选择一个Go语言的特性移植到其他语言中,他会选择接口,可以说,接口是Go语言整个类型系统的基石。
其他语言的接口
Go语言的接口并不是其他语言(C++、Java、C#等)中所提供的概念。在Go语言出现之前,接口主要作为不同组件之间的契约存在。对契约的实现是强制的,你必须申明你的确实现了该接口。在Go语言中,一个类只需要实现接口要求的所有的函数,我们就说这个类实现了该接口,尽管File类并没有继承IFile接口,甚至可以不用知道这些接口的存在,但是File类实现了这些接口,可以进行赋值,例如:
4.png
在Go语言中,类的继承树并没有任何的意义,你只需要知道这个类实现了那些方法,每个方法是啥含义就足够了。其次,实现类的时候,只需要关系自己应该提供那些方法,不用纠结接口需要拆分得多细才合理,接口由适用方按需求定义,而不用事前规划。还有就是为了实现一个接口而导入一个包,因为多引用一个外部的包,就意味着更多的耦合,接口由使用方按照自己需求来定义,使用方无需关心是否有其他模块定义过类似的接口。
接口赋值
接口赋值在Go语言中分为如下两种情况:
- 将对象实例赋值给接口;
- 将一个接口赋值给另一个接口;
先讨论将某种类型的对象实例赋值给接口,这要求该对象实例实现了接口所有方法,例如之前的Integer类型。如下:
type Integer int
func (a Integer) Less(b Integer) bool {
return a < b
}
func (a *Integer) Add(b Integer) {
*a +=b
}
相应的,我们定义接口LessAdder,如下:
type LessAdder interface {
Less(b Integer) bool
Add(b Integer)
}
假如我们定义一个Integer类型的对象实例,怎么将其赋值给LessAdder接口呢?
var a Integer = 1
var b LessAdder = &a
下面,我们再来讨论另一种情形:将一个接口赋值给另一个接口。在Go语言中,只要两个接口拥有相同的方法列表(次序不同不要紧),那么他们就是等同的,可以相互赋值。
Any类型
由于Go语言中任何对象实例都满足空接口interface{},所以interface{}看起来像是可以指向任何对象的Any类型,如下:
var v1 interface{} = 1
var v2 interface{} = "abc"