概述
面向对象编程(OOP)主要有四个核心特性:
- 封装
- 抽象
- 继承
- 多态
Go语言提供了不同的机制来实现这些OOP特性,但它的方式可能与传统的面向对象语言(比如Java或C++)有所不同。
封装:
在传统的面向对象语言中,封装通常通过使用类,以及访问修饰符(如private, protected, public)来实现。在Go语言中,封装是通过包(package)的概念来完成的,如果一个类型或函数的首字母是大写的,则它是公有的(即导出的),可以在包外部访问。如果首字母是小写的,则它是私有的,只能在定义它的包内部访问。
抽象:
抽象是指隐藏复杂性,只向用户暴露关键功能。在Go语言中,这通常是通过接口(interface)来实现的。接口定义了一个行为集(方法签名的集合),但不实现这些方法。具体的类型可以实现这些接口,通过这种方式,类型的复杂实现细节被抽象化了。
继承:
Go语言不支持传统意义上的类继承。但是有结构体(struct)的嵌入特性,可以使一个结构体嵌入到另一个结构体中,从而提供类似继承的特性,这允许从嵌入的结构体"继承"字段和方法。
多态:
多态指的是调用同一个接口的不同实现会产生不同的行为。在Go语言中,这是通过接口动态类型的概念来实现的。如果一个变量具有接口类型,那么实际的值可以是实现该接口的任何类型的值,调用时会基于值的实际类型来动态调用相应的方法。
Go OOP
Go语言中有接口的概念,但是接口是一种类型,抽象类型。
Go定义接口使用interface
关键字,例如:
// 接口定义格式:
type 接口类型名 interface {
方法1(方法1参数列表) 返回值列表
方法2(方法2参数列表) 返回值列表
方法3(方法3参数列表) 返回值列表
...
}
// 具体示例
type interface_name interface {
MethodA()
MethodB(b int)
MethodC(c int) int
}
interface的“继承”
Go中推荐使用组合而非继承,也就是嵌入interface。
比如:一家车企的在线车辆售卖系统中有订单管理,我们以订单作为我们示例。首先我们有一个基础的订单order
,然后有车辆订单,最后有电车订单,那我们在Go中可以这么来定义:
/ 有一句话:为了你Go语言的工作,你需要记住:interface是一种类型
// 订单接口
type Order interface {
// 获取订单号
GetNo() string
// 获取订单金额
GetAmount() uint32
// 获取订单备注
GetRemarks() string
}
//接口嵌套
// "继承", Go推荐使用组合来达到继承的目的
// 车辆订单接口
type VehicleOrder interface {
Order
GetInfo() string
}
// 接口嵌套
// "继承", Go推荐使用组合来达到继承的目的
// 电车订单接口
type ElectricVehicleOrder interface {
Order
GetEVInfo() string
}
我们可以看到,在VehicleOrder
和EletricVehicleOrder
中有我们刚才定义的order
,这样其实就完成了“继承”的目的。
实现接口
在Go中,一个struct
只要实现了一个接口的全部方法,那么就实现了这个接口。
比如:我们刚才定义的order
接口有3个方法,那么我们可以定义一个struct
来实现这个3个方法,实现了这三个方法就实现了这个接口:
// 实现基础订单
type BaseOrder struct {
}
// 获取订单号
func (o BaseOrder) GetNo() string {
return "Base Order No."
}
// 获取订单金额
func (o BaseOrder) GetAmount() uint32 {
return 1
}
// 获取订单备注
func (o BaseOrder) GetRemarks() string {
return "Base Order remarks"
}
我们还可以实现这个接口类型的另一个订单,代码如下:
// 另一个实现基础订单
type AnotherBaseOrder struct {
}
// 获取订单号
func (o *AnotherBaseOrder) GetNo() string {
return "Base Order No."
}
// 获取订单金额
func (o *AnotherBaseOrder) GetAmount() uint32 {
return 1
}
// 获取订单备注
func (o *AnotherBaseOrder) GetRemarks() string {
return "Base Order remarks"
}
值接收者和指针接收者
我们看刚才的两个struct
都实现了Order接口,但是两个唯一的差别就是一个有*
一个没有。
// 获取订单号
func (o BaseOrder) GetNo() string {
return "Base Order No."
}
// 获取订单号
func (o *AnotherBaseOrder) GetNo() string {
return "Base Order No."
}
第一个BaseOrder是值接收者,AnotherBaseOrder是指针接收者。这两个的差别就在于对结构体中定义的变量在方法内修改了以后是否可以传出。
*(o AnotherBaseOrder)是可以传出的,(o BaseOrder)**是无法传出的。
可以这么理解:函数传参是复制,(o BaseOrder)值接收者是将BaseOrder内存全部复制了一份到方法中,方法中的修改不会传出;AnotherBaseOrder指针接收者是可以的。
怎么选择?
如果你要修改struct中的成员,或者struct的成员占用内存比较大,那么就使用指针;如果你不想在方法中修改struct中的成员,那么就使用值。
接口嵌套
我们上面定义的VehicleOrder
和ElectricVehicleOrder
是使用的接口嵌套,当然我们也可以直接嵌套一个或多个接口来创建一个新接口,例如:
type NewVehicleOrder interface {
VehicleOrder
ElectricVehicleOrder
}
接口嵌套实现
下面代码就实现了嵌套接口定义的方法。
// 新车辆订单的具体实现,组合了车辆订单和电车订单的功能
type BasicNewVehicleOrder struct {
// 继承了 BaseVehicleOrder 的所有方法和属性
BaseVehicleOrder
// 继承了 BaseElectricVehicleOrder 的所有方法和属性
BaseElectricVehicleOrder
}
重写方法
// 新车辆订单的具体实现,组合了车辆订单和电车订单的功能
type BasicNewVehicleOrder struct {
// 继承了 BaseVehicleOrder 的所有方法和属性
BaseVehicleOrder
// 继承了 BaseElectricVehicleOrder 的所有方法和属性
BaseElectricVehicleOrder
}
// 重写 GetInfo 方法
func (bnvo *BasicNewVehicleOrder) GetInfo() string {
// 自定义返回信息或调用嵌入的 VehicleOrderImpl 结构体的 GetInfo 方法
return "New Basic Vehicle Order Info"
}
// 重写 GetEVInfo 方法
func (bnvo *BasicNewVehicleOrder) GetEVInfo() string {
// 自定义返回信息或调用嵌入的 ElectricVehicleOrderImpl 结构体的 GetEVInfo 方法
return "New Basic Electric Vehicle Order EVInfo"
}