乐趣区

关于go:Go是一门面向对象编程语言吗

本文首发自「慕课网」,想理解更多 IT 干货内容,程序员圈内热闻,欢送关注 ” 慕课网 ”!

作者:tonybai| 慕课网讲师

Go 语言曾经开源 13 年了,在近期 TIOBE 公布的 2023 年 3 月份的编程语言排行榜中,Go 再次冲入前十,相较于 Go 在 2022 年底的排名晋升了 2 个位次:

这些年对于 Go 在这两年开始飞起的“预言”也正在逐渐成为事实_,大家学习 Go 的激情也在疾速晋升。很多读者都是第一次接触 Go,你们中的很多是来自像 Java, Ruby 这样的 OO(面向对象)语言营垒的,很多童鞋学习 Go 之后的第一个问题便是:Go 是一门 OO 语言吗?在这篇博文中,咱们就来探讨一下。

一. 溯源
在公认的 Go 语言“圣经”《Go 程序设计语言》一书中,有这样一幅 Go 语言与其次要的先祖编程语言的亲缘关系图:

从图中咱们能够清晰看到 Go 语言的“继承脉络”:

  • 从 C 语言那里借鉴了表达式语法、管制语句、根本数据类型、值参数传递、指针等;
  • 从 Oberon- 2 语言那里借鉴了 package、包导入和申明的语法,而 Object Oberon 提供了办法申明的语法。
  • 从 Alef 语言以及 Newsqueak 语言中借鉴了基于 CSP 的并发语法。

咱们看到,从 Go 先祖溯源的状况来看,Go 并没有从纯面向对象语言比方 Simula、SmallTalk 等那里取经。Go 诞生于 2007 年,开源于 2009 年,那正是面向对象语言和 OO 范式大行其道的期间。不过 Go 设计者们感觉经典 OO 的继承体系对程序设计与扩大仿佛并无太多益处,还带来了较多的限度,因而在正式版本中并没有反对经典意义上的 OO 语法,即基于类和对象实现的封装、继承和多态这三大 OO 支流个性。

但这是否阐明 Go 不是一门 OO 语言呢?也不是!带有面向对象机制的 Object Oberon 也是 Go 的先祖语言之一,尽管 Object Oberon 的 OO 语法又与咱们明天常见的语法有较大差别。

就此问题,我还特意征询了 ChatGPT_,失去的回答如下:

ChatGPT 认为:Go 反对面向对象,提供了对面向对象范式基本概念的反对,但反对的伎俩却并不是类与对象。

那么针对这个问题 Go 官网是否有回应呢?

有的,咱们来看一下。

二. 官网声音

Go 官网在 FAQ 中就 Go 是否是 OO 语言做了简略回应:

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.

粗略翻译过去就是:

Go 是一种面向对象的语言吗?是,也不是。尽管 Go 有类型和办法,并且容许面向对象的编程格调,但却没有类型档次。Go 中的“接口”概念提供了一种不同的 OO 实现计划,咱们认为这种计划更易于应用,而且在某些方面更加通用。还有一些能够将类型嵌入到其余类型中以提供相似子类但又不等同于子类的机制。此外,Go 中的办法比 C ++ 或 Java 中的办法更通用:Go 能够为任何数据类型定义方法,甚至是内置类型,如一般的、“未装箱的”整数。Go 的办法并不局限于构造体(类)。此外,因为去掉了类型档次,Go 中的“对象”比 C ++ 或 Java 等语言更笨重。

“是,也不是”!咱们看到 Go 官网给出了一个“对两方都有害”的中庸的答复。那么 Go 社区是怎么认为的呢?咱们来看看 Go 社区的一些典型代表的观点。

三. 社区声音

Jaana Dogan 和 Steve Francia 都是前 Go 外围团队成员,他们在退出 Go 团队之前对“Go 是否是 OO 语言”这一问题也都有本人的观点阐述。

Jaana Dogan 在《The Go type system for newcomers》一文中给出的观点是:Go is considered as an object-oriented language even though it lacks type hierarchy,即“Go 被认为是一种面向对象的语言,即便它短少类型层次结构”。

而更早一些的是 Steve Francia 在 2014 年发表的文章《Is Go an Object Oriented language?》中的论断观点:Go,没有对象或继承的面向对象编程,也可称为“无对象”的 OO 编程模型。

两者表白的遣词不同,但含意却殊途同归,即Go 反对面向对象编程,但却不是通过提供经典的类、对象以及类型档次来实现的

那么 Go 到底是以何种形式实现对 OOP 的反对的呢?咱们持续看!

四. Go 的“无对象”

OO 编程经典 OO 的三大个性是封装、继承与多态,这里咱们看看 Go 中是如何对应的。

  1. 封装

封装就是把数据以及操作数据的办法“打包”到一个抽象数据类型中,这个类型封装暗藏了实现的细节,所有数据仅能通过导出的办法来拜访和操作。这个抽象数据类型的实例被称为对象。经典 OO 语言,如 Java、C++ 等都是通过类 (class) 来表白封装的概念,通过类的实例来映射对象的。相熟 Java 的童鞋肯定记得《Java 编程思维》一书的第二章的题目:“一切都是对象”。在 Java 中所有属性、办法都定义在一个个的 class 中。
Go 语言没有 class,那么封装的概念又是如何体现的呢?来自 OO 语言的初学者进入 Go 世界后,都喜爱“对号入座”,即 Go 中什么语法元素与 class 最靠近!于是他们找到了 struct 类型。
Go 中的 struct 类型中提供了对真实世界聚合形象的能力,struct 的定义中能够蕴含一组字段(field),如果从 OO 角度来看,你也能够将这些字段视为属性,同时,咱们也能够为 struct 类型定义办法(method),上面例子中咱们定义了一个名为 Point 的 struct 类型,它领有一个导出办法 Length:

type Point struct {x, y float64}

func (p Point) Length() float64 {return math.Sqrt(p.x * p.x + p.y * p.y)
}

咱们看到,从语法模式上来看,与经典 OO 申明类的办法不同,Go 办法申明并不需要放在申明 struct 类型的大括号中。Length 办法与 Point 类型建立联系的纽带是一个被称为 receiver 参数的语法元素。
那么,struct 是否就是对应经典 OO 中的类呢? 是,也不是!从数据聚合形象来看,仿佛是这样, struct 类型能够领有多个异构类型的、代表不同形象能力的字段 (比方整数类型 int 能够用来形象一个真实世界物体的长度,string 类型字段能够用来形象真实世界物体的名字等)。
但从领有办法的角度,不仅是 struct 类型,Go 中除了内置类型的所有其余具名类型都能够领有本人的办法,哪怕是一个底层类型为 int 的新类型 MyInt:

type MyInt int

func(a MyInt)Add(b int) MyInt {return a + MyInt(b)
}
  1. 继承

就像后面说的,Go 设计者在 Go 诞生伊始就从新评估了对经典 OO 的语法概念的反对,最终放弃了对诸如类、对象以及类继承档次体系的反对。也就是说:在 Go 中体现封装概念的类型之间都是“路人”,没有亲爹和儿子的关系的“牵绊”
谈到 OO 中的继承,大家更多想到的是子类继承了父类的属性与办法实现。Go 尽管没有像 Java extends 关键字那样的显式继承语法,但 Go 也另辟蹊径地对“继承”提供了反对。这种反对形式就是类型嵌入(type embedding),看一个例子:

type P struct {
    A int
    b string
}

func (P) M1() {}

func (P) M2() {}

type Q struct {c [5]int
    D float64
}

func (Q) M3() {}

func (Q) M4() {}

type T struct {
    P
    Q
    E int
}

func main() {
    var t T
    t.M1()
    t.M2()
    t.M3()
    t.M4()
    println(t.A, t.D, t.E)
}

咱们看到类型 T 通过嵌入 P、Q 两个类型,“继承”了 P、Q 的导出办法 (M1~M4) 和导出字段(A、D)。

不过理论 Go 中的这种“继承”机制并非经典 OO 中的继承,其外围类型 (T) 与嵌入的类型 (P、Q) 之间没有任何“亲缘”关系。P、Q 的导出字段和导出办法只是被晋升为 T 的字段和办法罢了,其本质是一种组合,是组合中的代理(delegate)模式的一种实现。T 只是一个代理(delegate),对外它提供了它能够代理的所有办法,如例子中的 M1~M4 办法。当外界发动对 T 的 M1 办法的调用后,T 将该调用委派给它外部的 P 实例来理论执行 M1 办法。

以经典 OO 实践话术去了解就是 TP、Q的关系不是 is-a,而是has-a 的关系。

  1. 多态

经典 OO 中的多态是尤指运行时多态,指的是调用办法时,会依据调用办法的理论对象的类型来调用不同类型的办法实现。上面是一个 C ++ 中典型多态的例子:

#include <iostream>

class P {
        public:
                virtual void M() = 0;};

class C1: public P {
        public:
                void M();};

void C1::M() {std::cout << "c1.M()\n";
}

class C2: public P {
        public:
                void M();};

void C2::M() {std::cout << "c2.M()\n";
}

int main() {
        C1 c1;
        C2 c2;
        P *p = &c1;
        p->M(); // c1.M()
        p = &c2;
        p->M(); // c2.M()
}

这段代码比拟清晰,一个父类 P 和两个子类 C1 和 C2。父类 P 有一个虚构成员函数 M,两个子类 C1 和 C2 别离重写了 M 成员函数。在 main 中,咱们申明父类 P 的指针,而后将 C1 和 C2 的对象实例别离赋值给 p 并调用 M 成员函数,从后果来看,在运行时 p 理论调用的函数会依据其指向的对象实例的理论类型而别离调用 C1 和 C2 的 M。

显然,经典 OO 的多态实现依靠的是类型的档次关系。那么对应没有了类型档次体系的 Go 来说,它又是如何实现多态的呢?Go 应用接口来解锁多态!

和经典 OO 语言相比,Go 更强调行为聚合与一致性,而非数据。因而 Go 提供了对相似 duck typing 的反对,即基于行为汇合的类型适配,但相较于 ruby 等动静语言,Go 的动态类型机制还能够保障利用 duck typing 时的类型平安。

Go 的接口类型实质就是一组办法汇合(行为汇合),一个类型如果实现了某个接口类型中的所有办法,那么就能够作为动静类型赋值给接口类型。通过该接口类型变量的调用某一办法,理论调用的就是其动静类型的办法实现。看上面例子:

type MyInterface interface {M1()
    M2()
    M3()}

type P struct {
}

func (P) M1() {}
func (P) M2() {}
func (P) M3() {}

type Q int 
func (Q) M1() {}
func (Q) M2() {}
func (Q) M3() {}

func main() {
    var p P
    var q Q
    var i MyInterface = p
    i.M1() // P.M1
    i.M2() // P.M2
    i.M3() // P.M3

    i = q
    i.M1() // Q.M1
    i.M2() // Q.M2
    i.M3() // Q.M3}

Go 这种无需类型继承档次体系、低耦合形式的多态实现,是不是用起来更轻量、更容易些呢!

五. Gopher 的“OO 思维”

到这里,来自经典 OO 语言营垒的小伙伴们是不是曾经找到了当初在入门 Go 语言时“感觉到顺当”的起因了呢!这种“顺当”就在于 Go 对于 OO 反对的形式与经典 OO 语言的差异:秉持着经典 OO 思维的小伙伴一上来就要建设的继承档次体系,但 Go 没有,也不须要。

要转变为正宗的 Gopher 的 OO 思维其实也不难,那就是“prefer 接口,prefer 组合,将习惯了的 is- a 思维改为 has- a 思维”。

六. 小结

是时候给出一些结论性的观点了:

  • Go 反对 OO,只是用的不是经典 OO 的语法和带档次的类型体系;
  • Go 反对 OO,只是用起来须要换种思维;
  • 在 Go 中玩转 OO 的思维形式是:“优先接口、优先组合”。

欢送关注「慕课网」官网帐号,咱们会始终保持内容原创,提供 IT 圈优质内容,分享干货常识,大家一起独特成长吧!

本文原创公布于慕课网,转载请注明出处,谢谢合作

退出移动版