Go 面向对象?

Go 不是纯粹的面向对象编程语言。摘自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)相比,它们中的一些在实现上有很大不同。

<!-- more -->

结构而不是类

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

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

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

oopoopemployeeemployee
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))
}
LeavesRemaining
oopmain.go
workspacepath -> oop -> employee -> employee.go  
workspacepath -> oop -> main.go  

main.go 内容如下:

package main

import "oop/employee"

func main() {  
    e := employee.Employee {
        FirstName: "Sam",
        LastName: "Adolf",
        TotalLeaves: 30,
        LeavesTaken: 20,
    }
    e.LeavesRemaining()
}
employeemain.go EmployeeLeavesRemaining()
workspacepath/bin/oopgo install oop
Sam Adolf has 10 leaves remaining 

New() 函数替代构造函数

main.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中的一个约定是命名一个函数 NewT(parameters),它创建一个类型T。这将像一个构造函数。如果包只定义了一个类型,那么Go中的一个约定就是命名这个函数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", e.firstName, e.lastName, (e.totalLeaves - e.leavesTaken))
}
type Employee structtype employee struct
LeavesRemaining()

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

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

go/src/constructor/main.go:6: undefined: employee.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  
New(parameters)