美文网首页
Go教程第二十四篇:结构体而非类

Go教程第二十四篇:结构体而非类

作者: 大风过岗 | 来源:发表于2020-05-18 16:21 被阅读0次

Go中的面向对象:结构体而非类

本文是《Go系列教程》的第二十四篇文章。

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并不支持类,但是它支持结构体。也可以向结构体中添加方法。这样的话,利用结构体我们就可以把数据的操作和数据绑定在一起,这就很像Java中的类。为了更好地理解它,我们来写一个例子。

为了理解为何结构体可以有效第替代类,我们先创建一个自定义的包。在~/Documents/下创建一个子目录,并命名为oop。同时我们初始化一个名为oop的Go模块,在oop目录下输入以下命令即可创建一个名为oop的模块。

go mod init oop

在oop里面再创建一个employee子目录。在employee子目录下,创建一个employee.go文件。目录的结构如下:

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

然后我们在employee.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\n", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

另外,在oop目录下,新建一个文件main.go,此时目录结构如下:

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

main.go中的内容如下:

package main

import "oop/employee"

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

运行此程序,将输出如下:

Sam Adolf has 10 leaves remaining

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

在上面,我们写的程序看样子没问题了,但是还有一点小问题。我们来看一下,当我们定义了一个零值的employee结构体时,都发生了什么。用下面这些代码,替换main.go中的代码。

package main

import "oop/employee"

func main() {
    var e employee.Employee
    e.LeavesRemaining()
}

上面程序的唯一变化,就是我们创建了一个零值的Employee。程序的输出如下:

  has 0 leaves remaining

正如你看到的,Employee的零值变量毫无用处。它根本没有有效的字段。在面向对象的编程语言如java中,此问题可以使用构造函数解决。可以使用一个参数化的构造函数创建一个有效的对象。

Go不支持构造函数。如果某个类型的零值没用的话,编程人员就不应该输出此类型,以防止在其他包中访问。同时还应该提供一个名为NewT(parameters)函数负责初始化类型T。
Go的传统是如果你要创建一个函数,此函数用于完成类型T的初始化的话,你就把它命名为NewT(parameters)。这其实就和构造函数的作用一样。如果某个包中只有一个类型,我们只需要把函数的名字定义为New(parameters)而不必定义为NewT(parameters)。

我们把上面的程序修改下,第一步,就是不输出Employee结构体,并创建一个New()函数,此函数会创建一个新的Employee。使用下面的代码替换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\n", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

我们做了几点重要的修改。我们把Employee的起始字母变成小写,把type Employee struct 改变为 type employee struct。这样做之后,我们就可以不输出employee结构体,并防止在其他包中访问。
其他包中访问employee。除非有特定的需要要输出它们,否则的话,把一个非输出的结构体的所有字段设置为非输出是最好的选择。因为我们不需要在employee包的外面访问employee结构体的字段,所以我们把所有的字段都设置为非输出。

现在既然employee是非输出的,那就不可能在其他包中创建Employee类型的值。因此,我们提供了一个导出函数New,此函数可以接收参数,并返回一个新创建的employee。

运行此程序,编译器会报:

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

这是因为employee包中的employee是非导出的,它不能在main函数中访问。因此,编译器就扔了一个错。这正是我们希望的。这样的话,其他包就不能创建零值的employee。
我们已经成功地阻止了创建没用的employee结构体变量。创建employee的唯一方式就是使用New函数。

使用下面的代码替换main.go中的内容:

package main

import "oop/employee"

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

上面程序的唯一变化之处,就是我们通过把必需的参数传递给New函数来完成employee的创建。

修改后的文件内容如下。

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\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)可以用来替换类的构造函数。

感谢您的阅读,请留下您珍贵的反馈和评论。Have a good Day!

备注
本文系翻译之作原文博客地址

相关文章

  • Go教程第二十四篇:结构体而非类

    Go中的面向对象:结构体而非类 本文是《Go系列教程》的第二十四篇文章。 Go是面向对象的吗? Go并不是一个纯面...

  • 2020-05-22

    类型属性与非类型属性的区别 结构体SomeStructure增加一个常量存储属性 类型属性调用 非类型属性调用 实...

  • Go语言探索 - 10(原创)

    上一篇文章主要学习了Go语言的结构体以及结构体指针,本篇文章主要学习Go语言的切片以及Map。 Go语言数组的长度...

  • 《Go语言四十二章经》第十八章 Struct 结构体

    《Go语言四十二章经》第十八章 Struct 结构体 作者:李骁 18.1结构体(struct) Go 通过结构体...

  • 结构体

    结构体初识 结构体指针 结构体的匿名字段 结构体嵌套 Go语言中的OOP

  • go结构体(struct)和方法(method)

    结构体(struct) go中没有对象这一概念,所以采用了结构体的概念,结构体在go中有着非常重要的位置。结构体是...

  • 第03天(复合类型)_结构体的基本使用

    29_结构体普通变量初始化 30_结构体指针变量初始化.go 31_结构体成员的使用:普通变量.go 32_结构体...

  • 第05天(异常、文本文件处理)_03

    11_通过结构体生成json.go 12_通过map生成json.go 13_json解析到结构体.go 14_j...

  • Go Struct

    Go语言通过自定义结构的方式来实现新的类型,结构体是类型中带有成员的复合类型。 Go语言使用结构体和结构体成员来描...

  • Go 语言程序设计——面向对象编程(5)

    结构体 Go 语言中创建自定义结构体最简单的方式是基于 Go 语言的内置类型创建 自定义类型也可以基于结构体创建,...

网友评论

      本文标题:Go教程第二十四篇:结构体而非类

      本文链接:https://www.haomeiwen.com/subject/zpnuohtx.html