Go 中的 OOP- 用结构体代替类

news/2024/10/17 15:27:09/

Go是面向对象的吗?

Go 不是一种纯粹的面向对象编程语言。这段摘录自 Go 的常见问题解答,回答了 Go 是否是面向对象的问题。

是也不是。虽然Go有类型和方法,并且允许面向对象的编程风格,但是没有类型层次结构。Go中的“接口”概念提供了一种不同的方法,我们认为这种方法易于使用,并且在某些方面更通用。还有一些方法可以将类型嵌入到其他类型中,以提供类似于子类化(但不完全相同)的东西。此外,Go中的方法比c++或Java中的方法更通用:它们可以为任何类型的数据定义,甚至可以为内置类型(如普通的“未装箱”整数)定义。它们并不局限于结构体(类)。此外,由于缺乏类型层次结构,Go中的“对象”感觉比c++或Java等语言要轻得多。

在接下来的教程中,我们将讨论如何使用 Go 实现面向对象的编程概念。与其他面向对象语言(例如 Java)相比,其中一些在实现上有很大不同。

使用结构体代替类

Go 不提供类,但它提供结构体。可以在结构上添加方法。这提供了将数据和对数据进行操作的方法捆绑在一起的行为,类似于类。

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

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

在里面创建一个子文件夹~/Documents/并命名oop

让我们初始化一个名为 的 go 模块oop。输入以下命令

go mod init oop  

在里面创建一个子文件夹employee。在employee文件夹内,创建一个名为employee.go

文件夹结构看起来像,

├── Documents
│   └── oop
│       ├── employee
│       │   └── employee.go
│       └── go.mod

请将employee.go的内容替换为以下内容,

package employeeimport (  "fmt"
)type Employee struct {  FirstName   stringLastName    stringTotalLeaves intLeavesTaken int
}func (e Employee) LeavesRemaining() {  fmt.Printf("%s %s has %d leaves remaining\n", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

在上面的程序中,第 1 行。指定该文件属于该employee包。

在第7 行声明Employee 结构 ,在14行声明一个名为的方法LeavesRemaining。现在我们有一个结构体和一个对结构体进行操作的方法,它们类似于类一样捆绑在一起。

oop文件夹内命名创建main.go

现在文件夹结构看起来像

├── Documents
│   └── oop
│       ├── employee
│       │   └── employee.go
│       ├── go.mod
│       └── main.go

main.go内容如下

package mainimport "oop/employee"func main() {  e := employee.Employee {FirstName: "Sam",LastName: "Adolf",TotalLeaves: 30,LeavesTaken: 20,}e.LeavesRemaining()
}

我们在第 3 行导入employee包。从第 12 行调用该结构体的方法LeavesRemaining()

该程序将打印输出,

Sam Adolf has 10 leaves remaining  

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

我们上面编写的程序看起来不错,但其中有一个微妙的问题。让我们看看当我们用零值定义员工结构时会发生什么。将 main.go的内容替换为以下代码,

package mainimport "oop/employee"func main() {  var e employee.Employeee.LeavesRemaining()
}

我们所做的唯一更改是创建零值Employee。该程序将输出,

  has 0 leaves remaining

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

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

Go 不支持构造函数。如果类型的零值不可用,则程序员的工作是取消导出该类型以防止从其他包访问,并提供一个名为NewT(parameters)的函数,该函数用所需的值初始化为类型T

Go 中的约定是命名一个创建NewT(parameters)的函数。这将充当构造函数。如果包只定义了一种类型,那么 Go 中的约定是将此函数命名New(parameters)

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

第一步是取消导出Employee结构并创建一个函数New()来创建新的Employee. 将代码替换employee.go 为以下内容,

package employeeimport (  "fmt"
)type employee struct {  firstName   stringlastName    stringtotalLeaves intleavesTaken 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\n", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

我们在这里做了一些重要的改变。我们在第 7行将 Employee 结构体的首字母改为小写。那就是我们已经改成type employee struct。通过这样做,我们成功地取消了employee结构的导出并阻止了其他包的访问。最好将未导出结构体的所有字段也设为未导出,除非有特定需要导出它们。由于我们不需要在包employee外的任何地方访问结构体的字段employee,因此我们也取消导出所有字段。

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

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

该程序仍然需要进行更改才能使其正常工作,但让我们运行它来了解到目前为止更改的效果。如果运行该程序,它将失败并出现以下编译错误,

# oop
./main.go:6:8: undefined: employee.Employee

这是因为我们的employee包中有一个未导出的employee,文件并且无法从main包中访问它。因此,编译器会抛出一个错误,指出该类型未在main.go 中定义。这正是我们想要的。现在没有其他包能够创建零值employee。我们已成功阻止创建无法使用的员工结构值。现在创建员工的唯一方法是使用该New函数。

main.go的内容替换为以下内容,

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

该文件的唯一更改是第 16 行。 我们通过将所需的参数传递给函数来创建一个新员工New

下面提供了进行所需更改后这两个文件的内容。

employee.go

package employeeimport (  "fmt"
)type employee struct {  firstName   stringlastName    stringtotalLeaves intleavesTaken 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\n", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

main.go

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

运行该程序将输出,

Sam Adolf has 10 leaves remaining  

因此你可以理解,虽然 Go 不支持类,但是可以有效地使用结构体来代替类,并且New(parameters)可以使用签名方法来代替构造函数。

这就是 Go 中的类和构造函数。请留下您的宝贵意见和反馈。


http://www.ppmy.cn/news/1185703.html

相关文章

HTML+CSS+JS实现计算器

🙈作者简介:练习时长两年半的Java up主 🙉个人主页:程序员老茶 🙊 ps:点赞👍是免费的,却可以让写博客的作者开心好久好久😎 📚系列专栏:Java全栈,…

redis6.0源码分析:跳表skiplist

文章目录 前言什么是跳表跳表(redis实现)的空间复杂度相关定义 跳表(redis实现)相关操作创建跳表插入节点查找节点删除节点 前言 太长不看版 跳跃表是有序集合zset的底层实现之一, 除此之外它在 Redis 中没有其他应用。…

Elasticsearch:标量量化 101 - scalar quantization 101

作者:BENJAMIN TRENT 什么是标量量化以及它是如何工作的? 大多数嵌入模型输出 float32 向量值。 虽然这提供了最高的保真度,但考虑到向量中实际重要的信息,这是浪费的。 在给定的数据集中,嵌入永远不需要每个单独维度…

国家数据局正式揭牌,数据专业融合型人才迎来发展良机【文末送书五本】

国家数据局正式揭牌,数据专业融合型人才迎来发展良机 国家数据局正式揭牌,数据专业融合型人才迎来发展良机 摘要书籍简介数据要素安全流通Python数据挖掘:入门、进阶与实用案例分析数据保护:工作负载的可恢复性Data Mesh权威指南分…

【论文精读1】MVSNet架构各组织详解

一、训练流程 1. 特征提取 提取N个输入图像的深层特征用作深度匹配 与传统三维重建方法类似,第一步是提取图像特征(SIFT等特征子),不同点在于本文使用8层的卷积网络从图像当中提取更深层的图像特征表示,网络结构如下…

[架构之路-246/创业之路-77]:目标系统 - 纵向分层 - 企业信息化的呈现形态:常见企业信息化软件系统 - 客户关系管理系统CRM

目录 前言: 一、企业信息化的结果:常见企业信息化软件 1.1 客户关系管理系统CRM 1.1.1 什么是客户关系管理系统 1.1.2 CRM总体架构 1.1.3 什么类型的企业需要CRM 1.1.4 创业公司在什么阶段需要CRM 1.1.5 研发型创业公司什么时候需要CRM 1.1.6 C…

Linux(Centos7)防火墙端口操作记录

1、nginx -t #Nginx配置文件检查 上述截图代表检查没问题 上述截图检查配置文件配置错误,并提示错误文件位置 2、systemctl restart nginx #重启Nginx 重启Nginx失败 3、systemctl status nginx.service #查看Nginx服务状态 80端口被占导致服务启动失败 4、n…

【孙哥说Spring5】第四章 Spring中的事务属性(Transaction Attribute)

什么是事务属性 属性:描述物体特征的一系列值性别 身高 体重 ...事务属性:描述事务特征的一系列值 1. 隔离属性 2. 传播属性 3. 只读属性 4. 异常属性如何添加事务属性 Transactional(isolation, propagation, readOnly, timeout, rollbackFor, noRoll…