欢迎来到第二十六讲。

GO是面向对象的吗?

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语言不提供类,但提供了结构体。方法可以被添加到结构体中,这提供了讲数据和方法捆绑在一起的行为,类似于一个类的实现。通过一个例子来更好理解。

employee

文件夹结构类似这样:

  workspacepath -> oop -> employee -> employee.go

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,结构体中有一个方法LeaveRemaining 这个方法计算和显示还剩下Employee的数量。现在我们有了一个结构体和方法,类似一个类一样。

然后在oop文件夹中创建一个mian.go文件,内容如下:

package main


import "oop/employee"


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

在例子中导入了employee包,运行之后显示:

Sam Adolf has 10 leaves remaining  

使用New()代替构造函数

上面的例子看起来是正常的,但存在一个小问题,让我们看一下如果定义了一个0值的结构体会发生什么,修改上面的代码为:

package main


import "oop/employee"
func main() {  
    var e employee.Employee
    e.LeavesRemaining()
}

结果输出:

 has 0 leaves remaining

如果创建了一个0值,那么输出就是无用的,没有一个可用的用户名也没有可用的数量。

在OOP语言中如java,这个问题可以通过构造函数解决。一个合法的对象可以通过使用有函数的构造函数来被创建。

NewT(parameters)

接下来修改一下Employee结构体:

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))
}

在这里我们有一些重要的改变,通过结构体名首字母改为小写e,结构体名称从Employee变为employee,这样做我们就是employee结构体限制为不可导出状态,从而阻止其他的包调用。通常设置一个不可导出的结构体所有成员都不能被导出是一个好的习惯(注意到上面的结构体各成员的首字母也变成小写了,如果一个结构体类型的名称以大写字母开头,则该结构体被导出,其他包可以访问它。同样地,如果结构体中的字段名以大写字母开头,则这些字段也可以被其他包访问。详见16讲),除非有明确指明需要导出他们。既然现在employee是不可导出的没那么在其他包中创建一个Employee结构体变量应该也是不可能的了,通过提供的New方法可以创建一个新的employee。不修改main.go的前提下,运行main.go此时会报错:

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

这是由于我们没有导出Employee所以编译器会报错。 现在没有其他包可以创建一个0值的employee结构体了,唯一的方法创建employee就是通过New函数

修改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!