go(基础06)——结构体取代类

1. 使用结构体

Go 不支持类,而是提供了结构体。结构体中可以添加方法。这样可以将数据和操作数据的方法绑定在一起,实现与类相似的效果。

我们创建一个自定义包,它帮助我们更好地理解,结构体是如何有效地取代类的。

在你的 Go 工作区创建一个名为 oop 的文件夹。在 oop 中再创建子文件夹 employee。在 employee 内,创建一个名为 employee.go 的文件。

文件夹结构会是这样:

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

employee.go 里的内容为如下所示的代码:

Go 复制代码
package employee
 
import (
 "fmt"
)
 
type Employee struct {
 FirstName   string
 LastName    string
 TotalLeaves int
 LeavesTaken int
}
 
func (e Employee) LeavesRemaining() {
 fmt.Printf("%s %s has %d leaves remaining", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

在上述程序里,第 1 行指定了该文件属于 employee 包。而第 7 行声明了一个 Employee 结构体。在第 14 行,结构体 Employee 添加了一个名为 LeavesRemaining 的方法。该方法会计算和显示员工的剩余休假数。于是现在我们有了一个结构体,并绑定了结构体的方法,这与类很相似。

接着在 oop 文件夹里创建一个文件,命名为 main.go。

现在目录结构如下所示:

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

workspacepath -> oop -> main.go

main.go 的内容如下所示:

Go 复制代码
package main
 
import "oop/employee"
 
func main() {
 e := employee.Employee {
  FirstName: "Sam",
  LastName: "Adolf",
  TotalLeaves: 30,
  LeavesTaken: 20,
 }
 e.LeavesRemaining()
}

我们在第 3 行引用了 employee 包。在 main()(第 12 行),我们调用了 Employee 的 LeavesRemaining() 方法。

由于有自定义包,这个程序不能在 go playground 上运行。你可以在你的本地运行,在 workspacepath/bin/oop 下输入命令 go install opp,程序会打印输出:

Sam Adolf has 10 leaves remaining

  1. 使用New()函数

将 main.go 的内容修改为如下代码:

Go 复制代码
package main
 
import "oop/employee"
 
func main() {
 var e employee.Employee
 e.LeavesRemaining()
}

我们的修改只是创建一个零值的 Employee 结构体变量(第 6 行)。该程序会输出:

has 0 leaves remaining

你可以看到,使用 Employee 创建的零值变量没有什么用。它没有合法的姓名,也没有合理的休假细节。

在像 Java 这样的 OOP 语言中,是使用构造器来解决这种问题的。一个合法的对象必须使用参数化的构造器来创建。

Go 并不支持构造器。如果某类型的零值不可用,需要程序员来隐藏该类型,避免从其他包直接访问。程序员应该提供一种名为 NewT(parameters) 的函数,按照要求来初始化 T 类型的变量。按照 Go 的惯例,应该把创建 T 类型变量的函数命名为 NewT(parameters)。这就类似于构造器了。如果一个包只含有一种类型,按照 Go 的惯例,应该把函数命名为 New(parameters), 而不是 NewT(parameters)。

让我修改一下原先的代码,使得每当创建 employee 的时候,它都是可用的。

首先应该让 Employee 结构体不可引用,然后创建一个 New 函数,用于创建 Employee 结构体变量。在 employee.go 中输入下面代码:

Go 复制代码
package employee
 
import (
 "fmt"
)
 
type employee struct {
 firstName   string
 lastName    string
 totalLeaves int
 leavesTaken int
}
 
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
 e := employee {firstName, lastName, totalLeave, leavesTaken}
 return e
}
 
func (e employee) LeavesRemaining() {
 fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

我们进行了一些重要的修改。我们把 Employee 结构体的首字母改为小写 e,也就是将 type Employee struct 改为了 type employee struct。通过这种方法,我们把 employee 结构体变为了不可引用的,防止其他包对它的访问。除非有特殊需求,否则也要隐藏所有不可引用的结构体的所有字段,这是 Go 的最佳实践。由于我们不会在外部包需要 employee 的字段,因此我们也让这些字段无法引用。

同样,我们还修改了 LeavesRemaining() 的方法。

现在由于 employee 不可引用,因此不能在其他包内直接创建 Employee 类型的变量。于是我们在第 14 行提供了一个可引用的 New 函数,该函数接收必要的参数,返回一个新创建的 employee 结构体变量。

这个程序还需要一些必要的修改,但现在先运行这个程序,理解一下当前的修改。如果运行当前程序,编译器会报错,如下所示:

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

这是因为我们将 Employee 设置为不可引用,因此编译器会报错,提示该类型没有在 main.go中定义。很完美,正如我们期望的一样,其他包现在不能轻易创建零值的 employee 变量了。我们成功地避免了创建不可用的 employee 结构体变量。现在创建 employee 变量的唯一方法就是使用 New 函数。

如下所示,修改 main.go 里的内容。

Go 复制代码
package main
 
import "oop/employee"
 
func main() {
 e := employee.New("Sam", "Adolf", 30, 20)
 e.LeavesRemaining()
}

该文件唯一的修改就是第 6 行。通过向 New 函数传入所需变量,我们创建了一个新的employee 结构体变量。

下面是修改后的两个文件的内容。

Go 复制代码
package employee
 
import (
 "fmt"
)
 
type employee struct {
 firstName   string
 lastName    string
 totalLeaves int
 leavesTaken int
}
 
func New(firstName string, lastName string, totalLeave int, leavesTaken int) employee {
 e := employee {firstName, lastName, totalLeave, leavesTaken}
 return e
}
 
func (e employee) LeavesRemaining() {
 fmt.Printf("%s %s has %d leaves remaining", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}
Go 复制代码
package main
 
import "oop/employee"
 
func main() {
 e := employee.New("Sam", "Adolf", 30, 20)
 e.LeavesRemaining()
}

运行该程序,会输出:

Sam Adolf has 10 leaves remaining

相关推荐
foxsen_xia1 小时前
go(基础08)——多态
算法·golang
leoufung1 小时前
用三色 DFS 拿下 Course Schedule(LeetCode 207)
算法·leetcode·深度优先
巨人张1 小时前
C++火柴人跑酷
开发语言·c++
ID_180079054731 小时前
基于 Python 的 Cdiscount 商品详情 API 调用与 JSON 核心字段解析(含多规格 SKU 提取)
开发语言·python·json
悟能不能悟1 小时前
Caused by: java.sql.SQLException: ORA-28000: the account is locked怎么处理
java·开发语言
foxsen_xia1 小时前
go(基础04)——mutex
golang
亦是远方2 小时前
南京邮电大学使用计算机求解问题实验一(C语言简单编程练习)
c语言·开发语言·实验报告·南京邮电大学
我是哈哈hh2 小时前
【Python数据分析】Numpy总结
开发语言·python·数据挖掘·数据分析·numpy·python数据分析
Michelle80232 小时前
24大数据 14-2 函数练习
开发语言·python