为什么一定要区分Go是不是面向对象的呢?
本文正在参加「金石计划」
Go面向对象吗?其实不重要!
关于Go,是不是面向对象的语言,网上有过很多分析的文章了,虽说都很对,但大多都在介绍Go语言是如何使用面向对象的三大特性的。而我,觉得官方给出的 Yes and no 的回答,很客观,很灵活。
至于什么是面向对象,我就不多说了,如果一定要说一句的话,我会说:面向对象是一种人类很容易理解的设计思想。
经常与之一起出现的,是面向过程,它更符合计算机的思维,也就是顺序思维。
更多关于面向对象的基本理念,我就不多赘述了,下面,先跟我一起来了解一下Go语言的“类”吧。
Go语言的"类"
- 直接来看两段很简单的代码:
// ---Go 语言的 “类”---
type People struct {
name string // 成员变量
age int
}
// ---Java 的类---
public class People {
String name; // 成员变量
int age;
}
在 Go 中,不像其他语言(本文多指Java),通常使用 class
表示为一类数据,它是使用 struct
来表示一类数据,抽象出这一类事物的状态和行为,用结构体来表述。
- 再来看两段代码
// ---Go 语言的 “成员方法”---
func (p *People) Eat() {
fmt.Println("叫Ciusyan吃饭了!!")
}
// ---Java 的成员方法---
public class People {
public void Eat() {
System.out.println("Ciusyan 在 Java 中吃饭");
}
}
如果是强面向对象的语言,比如 Java,所有方法必须定义在类中,即使是静态方法。而 Go 不一样啊,它的所有方法都是单独存在于外界的,但每一个结构体都可以通过一些标识符,明确说明哪些方法属于自己。
- 最后看两段代码
// --- Go 中的 “构造方法”
// NewDefaultPeople 可以看作是无参构造方法
func NewDefaultPeople() *People {
return NewPeople("", 0)
}
// NewPeople 可以看作是全参构造函数
func NewPeople(name string, age int) *People {
return &People{
name: name,
age: age,
}
}
// --- Java 中的构造方法 ---
public class People {
// 无参构造函数
public People() {
this(null, 0);
}
// 全参构造函数
public People(String name, int age) {
this.name = name;
this.age = age;
}
}
Go语言,没有构造函数这些概念,但是它也可以写一些函数,当作是构造函数来使用。可以用它们,快速的创建对象。但这里可以说明一下:其实只是我们习惯叫做对象罢了,准确来说,创建的是这个结构体的一个值。
但是这里真的想吐槽一下,Go语言没有函数重载,开发的时候,功能一样的方法,还得想一个不一样的名字。真的很不方便。😢
至此,相信你已经了解了Go语言的“类”,那么,它是不是面向对象的语言呢?
Go不是面向对象的
Go是不是面向对象,其实并不重要,但若你一定要我给出一个答案,我会说不是。先别着急反驳我,不妨听听我的观点,观点如下:
- Go 没有继承
- Go 不能直接多态
Go没有继承
是的,Go 语言没有继承。你可能会说不,下面一段代码,不就是继承吗?
type People struct {
name string // 看作是成员变量
age int
}
func (p *People) Eat() {
fmt.Println("叫Ciusyan吃饭了!!")
}
// Man 可以看作Man继承了People
type Man struct {
People
mustache string
}
func main() {
m := Man{}
m.Eat()
fmt.Printf("The Man name=%s, age=%d, 还有胡子了:%s\n",
m.name, m.age, m.mustache)
}
看起来还真的很像勒,Man 没有 age、name 两个属性、Eat()方法,但是它也能直接 Man.name、Man.age
,这不就是继承了People 这个父类的属性和方法了吗?
其实不然,上面看起来很像继承的操作,但仅是一个语法糖,其实上面的代码,在编译的时候,编译器会为我们生成相当于这样的代码:
func main() {
m := Man{}
m.People.Eat()
fmt.Printf("The Man name=%s, age=%d, 还有胡子了:%s\n",
m.People.name, m.People.age, m.mustache)
}
这样的调用,是不是和调用普通成员没什么两样?都是直接用 "."
来调用的(可直接访问的前提下)
也就是说,上面的写法,和下面这样写,实际上是一样的,只是上面哪里的字段名称是匿名的而已:
// Man 可以看作Man 组合了People
type Man struct {
p People // 组合 People
mustache string
}
func main() {
m := Man{}
m.p.Eat()
fmt.Printf("The Man name=%s, age=%d, 还有胡子了:%s\n",
m.p.name, m.p.age, m.mustache)
}
但这样写,就必须要明确指明,是调用 Man 的 p 属性的 Eat()
方法了。就不能使用上面那样的语法糖了,
好了,综上所述,其实Go语言是通过组合匿名字段的方式达到了类似继承的效果,本质上其实就是一块语法糖。至于Go为什么推崇使用组合,而不使用继承,也是为了更加灵活,删繁就简,便于理解吧!
也不能直接使用多态
而继承本质上是为了使用多态,连继承这么重要的特性都没有,也就不能直接使用多态了。也就不能使用形如下面的定义:
type People struct {}
func (p *People) Eat() {
fmt.Println("People 吃饭")
}
// Man 可以看作Man继承了People
type Man struct {
People
}
func main() {
// 父类的引用或者指针,指向之类对象
var p People = &Man{} // Error
p.Eat()
}
这样定义,明显是错误的表述,我们不能直接使用多态。利用多态的思想,衍生出了多少设计模式啊。如果没有多态,很多地方既不符合人类的思维方式(一个类可以有不同的实现),也会少了很多优雅的代码。
综上,既然 Go 的 struct,没有继承,也不能直接使用多态, 我就是可以说,它不是面向对象的语言。但是,这样的回答太牵强了,我自己都不太满意。其实,它是不是面向对象的语言,真的不重要。
Go是面向对象的
我一直在反复强调,Go 语言是不是面向对象的思想,真的不重要,理由如下,希望能给你带来一定的思考:
也能封装
很多人眼中的封装,可能是:
type People struct {
name string
}
func (p *People) GetName() string {
return p.name
}
func (p *People) SetName(name string) {
p.name = name
}
func main() {
p := People{}
p.SetName("Ciusyan")
println(p.GetName())
}
上面这样的?调整属性权限,让其不能直接被外界访问,只能通过Geter、和Seter来访问。这确实没错,这的确是封装的一部分内容。但它是次要的部分。封装的主要部分:将具体事务抽象成类的过程。
是的,这个过程才是最重要的,比如你要按照人类的思维来造车,你是如何把一辆车,抽象成一个类(struct)的过程, 才是封装的核心。要造车:
- 肯定得有底盘吧!
- 肯定得有发动机吧!
- 肯定得有车身吧!
type struct Car {
属性1 底盘
属性2 发动机
属性3 车身
}
那这辆车有什么功能呢?
- 至少能够发动吧!
- 至少得跑起来吧!
func (c *Car) Start() {
汽车启动
}
func (c *Car) Run() {
起床跑起来了!!!
}
上面只是简单的体现了一个抽象的过程,而这个过程,就是在将事物封装映射到代码世界中。而这样的过程,其实也更加符合人类思维。
所以,Go 语言也需要将很多问题抽象成同一类。那么,它是不是面向对象,还重要吗?🤔
它们的方法都会被自己标识
它们的方法都会被自己标识?是什么意思呢?又和面向对象有啥关系呢?
如果一个类所拥有的方法,会被它具体创建的实例给标识。
比如先来看一段面向对象的代码(Java):
// People 类的定义
public class People {
private String name;
public People(String name) {
this.name = name;
}
public void Show() {
System.out.println("我的名字是:" + this.name);
}
}
// Main方法中创建两个People对象
public static void main(String[] args) {
People ciusyan = new People("Ciusyan");
People cherLin = new People("CherLin");
ciusyan.Show(); // 我的名字是:Ciusyan
cherLin.Show(); // 我的名字是:CherLin
}
在这段代码中,我创建了两个People,都调用了它们的Show()方法,正确的展示了它们的名字。
这时候就被编译器惊艳到了啊,Show方法明明是一样的,它怎么那么聪明,知道是谁来调用自己了,他怎么知道是 ciusyan 这个人,调用了自己?把 ciusyan 的名字打印出来了。
其实原理很简单,因为People的Show()
方法只有一份,ciusyan这个实例在调用它的时候,会隐式的将自己,作为第一个参数,传入Show()方法中,什么意思呢?
比如下面的伪代码:
// People的Show方法
public void Show(People this) {
System.out.println("我的名字是:" + this.name);
}
// Main方法中创建两个People对象
public static void main(String[] args) {
People ciusyan = new People("Ciusyan");
People cherLin = new People("CherLin");
// 会隐式的将自己,当作第一个参数,传入Show方法中
ciusyan.Show(ciusyan); // 我的名字是:Ciusyan
cherLin.Show(cherLin); // 我的名字是:CherLin
}
看看上面的一段代码,只是我们是显式的传入了Show()方法中,如果是这样,那电脑肯定就知道,是谁这个时候在调用Show()方法了嘛!!
这不就是,类的方法,会被创建的实例给标识吗?说这个有什么用呢?那你再来看看下面Go语言的代码:
type People struct {
name string
}
func (this *People) Show() {
println("我的名字是:", this.name)
}
func main() {
var ciusyan People = People{name: "Ciusyan"}
var cherLin People = People{name: "CherLin"}
ciusyan.Show() // 我的名字是:Ciusyan
cherLin.Show() // 我的名字是:CherLin
}
我这里特地将People中Show() 方法 reciver
的名字,用 this
来标识,这不就是和大多数面向对象语言中,this 关键字一样的原理吗?
在Go中,在方法前面,用一个 Reciver
,可不仅表示,Show() 这个方法是属于 People 的。还有上面那个例子中一样的作用,用于标识这一次是谁在调用Show()方法。
那么,其他面向对象语言中的this思路,和Go语言基本一模一样,那它是不是面向对象的,还重要吗?🤔
也能实现多态
虽然上面说了不能直接使用多态,但是并没有说不能使用多态啊,在Go 语言中,可以利用接口来实现多态。(当然,Java也可以)
比如下面的代码:
// 人类,接口
type People interface {
// 工作的行为
Work()
}
// 男人 “类”
type Man struct {}
// 实现接口的方法
func (m *Man) Work() {
fmt.Println("Man 正在工作")
}
func main() {
var p People = &Man{}
// 现在是男人在工作
p.Work()
}
我们定义了People 这一 interface
,里面有一个Work()
方法,那么,是谁实现了这个Work方法,谁拥有这个Work()方法,我们根本不用关心,我们只关心,这个人可以工作(有Work方法)。
比如上面是使用一个男人,Man 来工作的,我们换一个人来继续工作,只需要:
// 女人 “类”
type WoMan struct {}
// 实现接口的方法
func (m *WoMan) Work() {
fmt.Println("WoMan 正在工作~~~")
}
func main() {
var p People = &WoMan{}
// 现在是女人在工作
p.Work()
}
这里说这个,并不是为了跟你介绍,什么是面向接口,其实,这不就是多态吗?一个 “类”,可以有不同的实现。 不论是 Java、还是 Go,它们的接口interface
类型,本质上也是一个类。
那么,既然 Go 也可以使用多态,它还是不是面向对象,还重要吗?🤔
真的不重要!!
综上,我从很多方面,写了一点自己的浅见。只想说,面向对象也只是一种思想,你说Go不是面向对象的语言,你在很多场景下,也得用到相关的思想。所以,它 "Yes and no" 面向对象。希望能够说服你,那么我们一起来总结一下吧~
- Go 语言中,没有对象(但是开发时通常叫做对象),没有类,也没有继承,也不能直接使用多态。
- 它通过组合匿名字段的手段,来达到类似继承的效果。
- 它只能通过接口,来实现的多态,使一个类可以有不同的实现。
- 将一类事物,抽象成struct的过程,这才是封装的主要部分
- Go语言 sturct 方法的写法,和其他面向对象语言中 this 的本质很像。
最后,再谈两个问题:
- 难道Go语言不是面向的语言,你不也要抽象封装吗?
- 难道Go语言不是面向的语言,你就不使用多态了吗?
以上的两个问题,就算你认为,Go不是面向对象的语言,你在开发的时候,也会用到一些面向对象的思想。Go语言通过一些手段,去掉了面向对象中复杂的部分,但也保留了基本的面向对象特性。所以可以说,它是不是面向对象,真的不重要!!!