Go是面向对象的吗?
Go不是一种纯粹的面向对象的编程语言。这段摘录自Go的FAQ,回答了Go是否是面向对象的问题。
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).
在接下来的教程中,我们将讨论如何用Go实现面向对象的编程概念。与其他面向对象语言(如Java)相比,其中一些概念在实现上有很大不同。
结构而不是类
Go 不提供类,但提供结构。方法可以被添加到结构上。这提供了将数据和对数据进行操作的方法捆绑在一起的行为,类似于一个类。
让我们马上从一个例子开始,以便更好地理解。
在这个例子中,我们将创建一个自定义包,因为它有助于更好地理解结构如何有效地替代类。
在E:\backend\Go_backend
内创建一个子文件夹,并将其命名为oop
。
让我们初始化一个名为oop
的go模块。在oop
目录下输入以下命令,创建一个名为oop
的go模块。
1 | go mod init oop |
在oop
里面创建一个子文件夹employee
。在employee
文件夹内,创建一个名为employee.go
的文件。
文件夹结构将看起来像。
1 | ├── Documents |
请将employee.go
的内容替换为以下内容。
1 | package employee |
在上面的程序中,第1行指定该文件属于employee
包。在第7行中声明了一个Employee
结构。在第14行中为Employee结构添加了一个名为LeavesRemaining
的方法。这个方法计算并显示一个Employee
的剩余假期数。现在我们有了一个结构和一个对结构进行操作的方法,就像一个类一样捆绑在一起。
在oop
文件夹中创建一个名为main.go
的文件。
现在的文件夹结构看起来像。
1 | ├── Documents |
main.go
的内容提供如下。
1 | package main |
我们在第3行导入了雇员包。在main()
的第12行调用了Employee
结构中的LeavesRemaining()
方法。
这个程序不能在操场上运行,因为它有一个自定义包。如果你在本地运行这个程序,发出go install
和oop
的命令,程序会打印输出。
1 | Sam Adolf has 10 leaves remaining |
如果你不确定如何运行这个程序,请访问https://golangbot.com/hello-world-gomod/,了解更多。
用New()函数代替构造函数
我们上面写的程序看起来还不错,但其中有一个微妙的问题。让我们看看当我们用零值定义雇员结构时会发生什么。用以下代码替换main.go
的内容。
1 | package main |
我们所做的唯一改变是在第6行创建一个零值的Employee。这个程序将输出。
1 | has 0 leaves remaining |
正如你所看到的,用Employee
的零值创建的变量是不可用的。它没有有效的FirstName
和LastName
,也没有有效的休假信息。
在其他OOP语言中,比如java,这个问题可以通过使用构造函数来解决。通过使用参数化的构造函数可以创建一个有效的对象。
Go不支持构造函数。如果一个类型的零值不能使用,那么程序员的工作就是取消对该类型的输出,以防止其他包的访问,同时提供一个名为NewT(parameters)
的函数,用所需的值来初始化T
这个类型。Go中的一个惯例是将创建T
类型值的函数命名为NewT(parameters)
。这将作为一个构造函数。如果包只定义了一种类型,那么Go的惯例是将这个函数命名为New(parameters)
而不是NewT(parameters)
。
让我们对我们写的程序进行修改,以便每次创建一个雇员时都能使用。
第一步是取消Employee
结构,并创建一个函数New()
来创建一个新的Employee
。用下面的代码替换Employee.go
中的代码。
1 | package employee |
我们在这里做了一些重要的修改。我们在第7行将Employee结构的开头字母e
改为小写。也就是说,我们将type Employee struct
改为type employee struct
。通过这样做,我们成功地取消了employee
结构的导出,防止了其他包的访问。让未导出结构的所有字段也不导出是一个很好的实践,除非有特定的需要导出它们。由于我们不需要在employee
包之外的地方访问employee
结构的字段,所以我们也取消了所有字段的导出。
我们在LeavesRemaining()
方法中相应地改变了字段名。
现在,由于employee
是未导出的,所以不可能从其他包中创建employee
类型的值。因此,我们在第14行提供了一个导出的New
函数,该函数接受所需的参数作为输入,并返回一个新创建的employee
。
这个程序还有待修改,但让我们运行这个程序来了解目前的修改效果。如果运行这个程序,它将出现以下编译错误。
1 | # oop |
这是因为我们在employee
包中有一个未导出的employee
,它不能从main
包中访问。因此,编译器抛出了一个错误:main.go
中没有定义这个类型。完美。这正是我们想要的。现在没有其他包能够创建一个零值的employee
了。我们成功地防止了一个不可用的employee
结构值被创建。现在创建employee的唯一方法是使用New
函数。
将main.go
的内容替换为以下内容。
1 | package main |
这个文件的唯一改动是第6行。我们通过向New
函数传递所需的参数,创建了一个新的雇员。
下面是两个文件在做了必要的修改后的内容。
employee.go
1 | package employee |
main.go
1 | package main |
运行这个程序将输出。
1 | Sam Adolf has 10 leaves remaining |
因此你可以理解,尽管Go不支持类,但结构体可以有效地代替类,签名为New(parameters)
的方法可以用来代替构造函数。
Go中的类和构造函数就介绍到这里。请留下您的宝贵意见和反馈:)。