Part 26 Go的面向对象 - 结构体代替类

欢迎来到 Golang系列教程

文章目录

  • Go是面向对象的吗?
  • 结构体代替类
  • New() 函数代替构造函数

Go是面向对象的吗?

Go不是一个纯粹的面向对象的程序语言。这摘抄自Go的FAQs,回答Go是否是面向对象的语言的答案。

是,也不是。尽管 Go 有类型和方法,允许面向对象风格的编程,但没有类型层次结构,Go中 “interface” 的概念提供了一种我们认为易于使用且在某些方面更为通用的不同方法。还有一些方法可以将类型嵌入到其他类型中,以提供类似但不完全相同的子类化。此外,Go 的方法比 C++ 或 Java 更通用:它们可以被任何类型的数据定义,甚至是内置类型,例如文本,“”unboxed" 整型。它并不限制结构体(类)。

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

结构体代替类

Go 没有提供类,但它提供了 结构体。方法可以被加到结构体上。这提供了将数据和行为绑定到一起的行为,类似于类。

我们现在开始一个例子以更好地理解它。

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

在你的 Go 工作空间创建一个文件夹并命名为 oop。在 oopt 中创建一个子文件夹 employee。在 employee 文件夹中,创建一个名为 employee.go 的文件。

文件结构看起来像

workspacepath 
      | -  oop
          | -  employee
                | - employee.go

使用下面代码替换 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", e.FirstName, e.LastName, (e.TotalLeaves - e.LeavesTaken))
}

在上面的程序,第一行指定了这个文件属于 employee 包。一个 Employee 结构体在第 7 行被声明。方法 LeavesRemaining 在第 14 行被添加到 Employee 结构体。它计算并展示 employee 拥有的剩余的叶子数。现在我们有一个结构体和一个和结构体绑定到一起的方法,类似类。

oop 文件夹内创建一个名为 main.go 的文件。

现在文件结构看起一像:

workspacepath 
      | -  oop
          |- main.go
          | - employee
              | - employee.go

main.go 的内容如下:

package main

import "oop/employee"

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

我们在第 3 行导入 employee 包。Employee 结构体的方法 LeavesRemaining() 在第 12 行的 main() 中被调用。

这个程序不可以在 playground 上运行,因为它有一个自定义包。如果你在你本地运行通过 go install oop 命令该程序。程序将打印

Sam Adolf has 10 leaves remaining  

New() 函数代替构造函数

上面我们写的程序看起来是好的, 但是它有一个小问题。让我们看一下当employee结构体定义是零值会发生什么。使用下列代码修改 main.go 的内容。

package main

import "oop/employee"

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

我们做的唯一的修改是在第 6 行创建一个零值的 Employee。程序将打印

has 0 leaves remaining

正如你看到的,使用零值创建变量 Employee 是不可用的。它没有一个合法的姓名,也没有合法的剩余结节。

在其他面向对象语言,比如Java,这个问题可以通过使用构造函数解决。一个合法的对象可以通过使用带参的构造函数创建。

Go 不支持构造函数。如果一个类型的零值是不可用的,阻止未导出类型从其他包访问和提供一个名为 NewT(parameters的函数来初始化需要值的类型 T 是程序员的工作。Go的一个惯例是命名一个函数,它用 NewT(parameters) 为创建一个类型为T 的值。这表现得像一个构造函数。如果包仅定义一个类型,Go的惯例是仅使用 New(parameters) 代表 NewT(parameters)来命令这个函数。

我们来修改一下我们所写的程序,以使每次 employee 被创建,它是可用的。

第一步,取消导出 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", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}

我们在这儿做一些重要的修改。我们修改 Employee 结构体的首字段为小字的 e。从 type Employee structtype employee struct 是我们所修改的地方。通过做这些,我们成功地取消 employee 结构体的导出,阻止其他包的访问。除非有特殊需要导出它们,否则将未导出结构的所有字段都取消导出是一种很好的做法。由于我们在包外的任何地方不需要 employee 结构体的字段,所有我们取消所有字段的导出。

我们已经在 LeavesRemaining() 方法相应地修改了字段名。

现在,由于 employee 是未导出的,从其他包创建 Empoyee 类型的值是不可能的。由于我们在第 14 行提供了一个导出的 New() 函数。它使用需要内部需要的参数并返回一个新创建的 empolyee。

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

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

因为我们未导出 Employee,所以编译器抛出错误,该类型未在 main.go 中定义,完美。正如我们想要的。现在没有其他包可以创建一个空值的 employee。我们从创建时就成功地阻止了不可使用的 employee 结构体值。现在创建 employee 的唯一的方法是使用 New 函数。

使用下面替换 main.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))
}

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) 可以被用来代替构造函数。

**下一教程 - 组合代表继承 **

你可能感兴趣的:([译]Golang教程系统)