[关闭]
@phper 2018-03-13T10:06:01.000000Z 字数 4191 阅读 4273

26.用结构体替代类-golang中的面向对象

Golang

原文: https://golangbot.com/structs-instead-of-classes/


欢迎访问Golang 系列教程中的第26章。

是否面向对象?

go不是一个纯面向对象的编程语言。从 "Go's FAQs" (go 常见问题解答)中取出的摘录回答了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 不提供类, 但它提供结构。可以在结构上添加方法。这提供了将操作数据和方法与类一起进行绑定的行为。

让我们从一个例子开始, 以更好地理解。

在本示例中, 我们将创建一个自定义包, 因为它有助于更好地了解结构如何有效地替换类。

在 Go 工作区内创建一个文件夹, 并将其命名为oop。在oop内创建子文件夹employee。在employee文件夹内, 创建名为 "employee" 的文件employee.go

文件夹结构看起来像,

   workspacepath -> oop -> employee -> employee.go

请用下面的内容替换 employee.go

  1. package employee
  2. import (
  3. "fmt"
  4. )
  5. type Employee struct {
  6. FirstName string
  7. LastName string
  8. TotalLeaves int
  9. LeavesTaken int
  10. }
  11. func (e Employee) LeavesRemaining() {
  12. fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
  13. }

在上面的程序中, 第一行指定此文件属于employee包。雇员结构在7行中声明。名为LeavesRemaining的方法被添加到14行的Employee结构中。这将计算并显示雇员剩余的叶数。现在, 我们有一个结构和一个方法, 在一个结构捆绑在一起类似于一个类。

oop文件夹中创建名为main.go 的文件。

现在文件夹结构看起来像,

workspacepath -> oop -> employee -> employee.go  
workspacepath -> oop -> main.go  

下面提供了main.go的内容。

  1. package main
  2. import "oop/employee"
  3. func main() {
  4. e := employee.Employee {
  5. FirstName: "Sam",
  6. LastName: "Adolf",
  7. TotalLeaves: 30,
  8. LeavesTaken: 20,
  9. }
  10. e.LeavesRemaining()
  11. }

我们将employee包导入3行。Employee结构的LeavesRemaining()方法从main()中的12行调用。

此程序不能在操场上运行, 因为它具有自定义包。如果您在本地运行此程序, 则通过发出命令 "go install oop " 后跟workspacepath/bin/oop, 该程序将打印输出,

Sam Adolf has 10 leaves remaining  

New () 函数而不是构造函数

我们上面写的程序看起来很好, 但它有一个微妙的问题。让我们看看当定义一个零值的雇员结构时会发生什么。将main.go的内容更改为以下代码,

  1. package main
  2. import "oop/employee"
  3. func main() {
  4. var e employee.Employee
  5. e.LeavesRemaining()
  6. }

我们所做的唯一更改是在6行中创建零值Employee。这个程序将输出,

has 0 leaves remaining

正如您所看到的, 使用Employee的零值创建的变量是不可用的。它没有有效的名字, 姓氏, 也没有有效的休假细节。

在其他 OOP 语言 (如 java) 中, 可以使用构造函数来解决此问题。可以使用 parameterised 构造函数创建有效的对象。

go 不支持构造函数。如果类型的零值不可用, 则程序员的任务是 unexport 该类型以防止其他包的访问, 并提供一个名为NewT(parameters)的函数, initialises 类型T与所需的值.它是一个用于命名函数的约定, 它创建T类型的值为NewT(parameters)。这将像一个构造函数。如果包只定义了一种类型, 那么它就是一个约定, 它只用于New(parameters)而不是NewT(parameters) 。.

让我们对我们编写的程序进行更改, 以便每次创建员工时都可以使用它。

第一步是 unexportEmployee结构, 并创建一个函数New() , 它将创建一个新的Employee。用以下内容替换employee.go中的代码,

  1. package employee
  2. import (
  3. "fmt"
  4. )
  5. type employee struct {
  6. firstName string
  7. lastName string
  8. totalLeaves int
  9. leavesTaken int
  10. }
  11. func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
  12. e := employee {firstName, lastName, totalLeave, leavesTaken}
  13. return e
  14. }
  15. func (e employee) LeavesRemaining() {
  16. fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
  17. }

我们在这里做了一些重要的改动。我们已将雇员结构的起始字母e转换为小写, 即我们已将type Employee struct更改为type employee struct。通过这样做, 我们成功地 unexported 了employee结构并阻止了其他程序包的访问。这是一个很好的做法, 使 unexported 结构的所有字段都 unexported, 除非有特定的需要导出它们。由于我们不需要包外任何位置的employee结构的字段, 所以我们也 unexported 了所有字段。

我们在LeavesRemaining()方法中相应地更改了字段名。

现在, 由于employee是 unexported 的, 因此无法从其他包中创建Employee类型的值。因此, 我们在14行中提供了一个导出的New函数, 它以所需参数作为输入, 并返回新创建的员工。

这个程序仍然有改变, 使其工作, 但让运行这一点, 以了解变化的影响到目前为止。如果运行此程序, 将失败, 并出现以下编译错误,

go/src/constructor/main.go:6: undefined: employee.Employee 

这是因为我们有 unexportedEmployee, 因此编译器会抛出错误, 在main.go中未定义此类型。完美.正是我们想要的。现在没有其他程序包可以创建零值employee。我们成功地防止了无法使用的员工结构值的创建。现在创建员工的唯一方法是使用New函数。

将main.go的内容替换为如下所示,

  1. package main
  2. import "oop/employee"
  3. func main() {
  4. e := employee.New("Sam", "Adolf", 30, 20)
  5. e.LeavesRemaining()
  6. }

此文件的唯一更改为6行。通过将所需参数传递给New函数, 我们创建了一个新员工。

这两个文件的内容在进行必要的更改后,

employee.go

  1. package employee
  2. import (
  3. "fmt"
  4. )
  5. type employee struct {
  6. firstName string
  7. lastName string
  8. totalLeaves int
  9. leavesTaken int
  10. }
  11. func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
  12. e := employee {firstName, lastName, totalLeave, leavesTaken}
  13. return e
  14. }
  15. func (e employee) LeavesRemaining() {
  16. fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
  17. }

main.go

  1. package main
  2. import "oop/employee"
  3. func main() {
  4. e := employee.New("Sam", "Adolf", 30, 20)
  5. e.LeavesRemaining()
  6. }

运行此程序将输出,

Sam Adolf has 10 leaves remaining  

因此, 您可以理解, 虽然不支持类, 但可以有效地使用结构而不是类和签名方法New(parameters)可以在构造函数的位置使用。

以上就是go中的类和构造函数。祝你今天开心。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注