原文来自:https://golangbot.com/structs-instead-of-classes/
Go不是存粹的面向对象编程语言从Go在FAQs上的摘要回答了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不提供类概念,只有结构体struct,方法method可以被加入到结构体中。这就提供了绑定数据和方法的行为,类似class的实现。
为了更好的理解,我们举个例子。
我们将创建一个custom包,它将帮助沃恩更好的理解结构体可以有效的代替类。
首先,在Go的工作目录下创建一个命名为oop的文件夹,在oop下创建子目录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,并给结构体添加方法LeavesRemainings,用来计算并显示还有多少个员工。现在我们实现了一个结构体和他的方法,类似一个类的行为。
在oop目录下,新建main.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()
}
程序先导入empolyee包,然后结构体Employee调用方法Leaves Remaining()。上述程序不能在playground上运行,这是因为自定义了custom包,需要在自己的环境上运行。执行结果:
Sam Adolf has 10 leaves remaining
上述程序看起来是正确的,但是有一点小瑕疵。我们来看看,如果定义一个零值变量结构体会发生什么。修改main.go代码如下:
package main
import "oop/employee"
func main() {
var e employee.Employee
e.LeavesRemaining()
}
程序执行结果:
has 0 leaves remaining
正如你所看到的,声明了一个零值结构体变量,结果是不可用的,缺少可用的姓名和员工数量。
其他面向对象语言像java,可以通过构造函数来解决这个问题。一个合法的对象可以通过带参数的构造函数来构建。
Go没有提供构造函数。如果一个类型的零值是不合法的,程序因该禁止其他包导入这么使用,并且提供一个函数NewT(参数)去初始化带参数的类型T的的变量。Go语言很容易实现用NewT(parameters)函数去创建类型T变量。如果一个包只定义一中类型,可以用New()代替NewT()。
我们来修改程序,保证新建的每一个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修改为小写字母e,结构体变成私有变量,这样可以防止该变量被其他包直接访问。这是一个很好的措施,如果变量不希望被其他包导入,有效的方法是将变量私有化。
现在employee结构私有化,其他包不能直接创建Employee类型的变量。因此我们需要提供一个公有方法New用来创建带参数的employee变量。
程序还需要再修改。我们直接运行程序将会编译报错:
go/src/constructor/main.go:6: undefined: employee.Employee
这是因为Employee已经私有化,因此编译报错类型未定义。这正是我们想要的效果。没有其他包可以直接创建一个零值变量。我们成功实现了防止创建一个无效的employee结构体变量。唯一创建一个employee变量的方法是使用New函数。
修改main.go代码如下:
package main
import "oop/employee"
func main() {
e := employee.New("Sam", "Adolf", 30, 20)
e.LeavesRemaining()
}
两个文件的最终代码如下:
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)方法可以替代构造函数。
这就是Go语言的类和构造函数。Have a good day.
ps:新手翻译,欢迎大家指正!
Golang教程第27节--组合取代继承