我正在尝试学习 Go,但我一直在苦苦思索它的一些概念,这些概念与其他语言相比有不同的应用。
假设我有一个结构
type Vehicle struct {
Seats int
}
我现在想要另一个结构体,它嵌入
Vehicle
:
type Car struct {
Vehicle
Color string
}
据我了解,
Car
结构现在嵌入Vehicle
。
现在我想要一个可以使用任何车辆
的功能func getSeats(v Vehicle){
return v.Seats
}
但是每当我尝试通过
Car
:
getSeats(myCar)
我收到以下错误:
cannot use myCar (value of type Car) as Vehicle value in argument to getSeats
myCar
有一个 Seats
属性!
由此我明白嵌入与继承不同。
我的问题是;是否有类似于 c++ 的结构继承,其中函数可以采用基本结构,或者这是 Go 处理完全不同的东西?我如何在“Go way”中实现这样的事情?
就像你提到的,Go 没有典型意义上的继承。嵌入实际上只是语法糖。
嵌入时,您可以向结构体添加一个与要嵌入的类型名称完全相同的字段。嵌入结构的任何方法都可以在嵌入它们的结构上调用,这只不过是转发它们。
一个问题是,如果嵌入另一个结构的结构已经声明了一个方法,那么它将优先于转发它,如果你想这样想的话,这允许你对函数进行排序。
正如您所注意到的,即使
Car
嵌入 Vehicle
,我们也不能将 Car
用作 Vehicle
,因为它们严格来说不是同一类型。但是任何嵌入 Vehicle
的结构都将具有由 Vehicle
定义的所有方法,因此,如果我们定义一个 Vehicle
实现的接口,则所有嵌入 Vehicle
的类型也应该实现该接口。
例如:
package main
import (
"fmt"
)
type Seater interface {
Seats() int
}
type Vehicle struct {
seats int
}
func (v *Vehicle) Seats() int {
return v.seats
}
type Car struct {
Vehicle
Color string
}
type Bike struct {
Vehicle
Flag bool
}
// A bike always has 1 seat
func (b *Bike) Seats() int {
return 1
}
type Motorcycle struct {
Vehicle
Sidecar bool
}
// A motorcycle has the base amounts of seats, +1 if it has a side car
func (m *Motorcycle) Seats() int {
return m.Vehicle.seats + 1
}
func getSeats(v Seater) int {
return v.Seats()
}
func main() {
fmt.Println(getSeats(&Bike{
Vehicle: Vehicle{
seats: 2, // Set to 2 in the Vehicle
},
Flag: true,
}))
fmt.Println(getSeats(&Motorcycle{
Vehicle: Vehicle{
seats: 1,
},
Sidecar: true,
}))
fmt.Println(getSeats(&Car{
Vehicle: Vehicle{
seats: 4,
},
Color: "blue",
}))
}
打印:
1
2
4
在
Bike
的情况下,会调用 Bike.Seats
方法,这就是为什么它会返回 1
,即使其 seats
的 Vehicle
值为 2
。
在
Motorcycle
的情况下,也会调用 Motorcycle.Seats
方法,但在这里我们可以访问嵌入类型并仍然使用它来获取结果。
在
Car
的情况下,会调用 Vehicle.Seats
方法,因为 Car
不会“覆盖”Seats
。
这是我刚开始使用 Go 时关心的事情。我认为使用 Java 等 OOP 语言的概念是自然的做事方式。事实并非如此,Go 不是面向对象的,它不通过继承实现多态性。最后,Go 支持的组合从继承中获取了好的东西,并删除了增加更多开销而不是有效帮助的东西。 Go 对多态性的回答是接口。 Go 试图变得简单,你通常只能通过一种明显的方式来实现目标。假设您有一个像这样的 Java 类:
class Base {
private int attribute;
public int getAttribute() {
return this.attribute;
}
}
class Something extends Base {}
在java中,每个对象都在堆上,并且有指向它的指针。它可以是
null
,这也意味着当你传递它时它具有相同的大小。这就是为什么你可以更自由地传递对象的原因之一(无论你传递Base
还是Something
,只要参数类型是超类,它就会编译)。 Go 的设计目的是更有效地管理内存,并且更多地使用堆栈而不是堆,甚至堆的碎片也更少。当您声明 struct
时,该结构的大小由它所保存的数据决定,因此您无法将其传递到嵌入结构所属的位置。举个例子吧:
type Base struct {
attribute int32
}
func (b *Base) Attribute() int32 {
return b.attribute
}
type Something struct {
garbage int32
Base
}
虽然
Base
有4个字节,Something
有8个字节,并且attribute
有不同的偏移量。如果编译器允许您传递 Something
来代替 Base
,您将访问 garbage
而不是 attribute
。如果你想获得这种行为,你必须使用接口。幸运的是,您需要声明的是:
type BaseInterface interface {
Attribute() int32
}
现在您有了这个接口,您可以在嵌入 Base 的所有结构上创建通用函数(除非它还嵌入了在同一级别上具有方法名称
Attribute
的其他内容),如下所示:
func DoubleOfBaseAttribute(base BaseInterface) int32 {
return base.Attribute() * 2
}
这也称为动态调度,C++ 或 Java 等语言隐式使用它。
在 GO 中,通过组合实现继承,通过接口实现多态。我认为下面的例子可以澄清这一点。
package main
import (
"fmt"
)
// describe is a common behavior that Shape and its sub-classes
// Circle, Rectangle and Square have.
type ShapeDescriber interface {
describe() string
}
// Shape can be considered as a base class.
type Shape struct {
name string
}
func (s *Shape) describe() string {
return s.name
}
// Circle can be considered as a derived class of Shape.
type Circle struct {
Shape
radius float32
}
// Rectangle can be considered as a derived class of Shape.
type Rectangle struct {
Shape
height float32
width float32
}
func (r *Rectangle) describe() string {
return fmt.Sprintf("%s, height: %.2f, width: %.2f", r.Shape.name, r.height, r.width)
}
// Square can be considered as a derived class of Rectangle.
type Square struct {
Rectangle
side float32
}
func (q *Square) describe() string {
return fmt.Sprintf("%s, side: %.2f", q.Shape.name, q.Rectangle.height)
}
func main() {
var s ShapeDescriber
s = &Circle{
Shape: Shape{name: "Circle"},
radius: 1.0,
}
fmt.Println(s.describe())
s = &Rectangle{
Shape: Shape{name: "Rectangle"},
height: 1.0,
width: 2.0,
}
fmt.Println(s.describe())
s = &Square{
Rectangle: Rectangle{Shape{"Square"}, 1.0, 1.0},
side: 1.0,
}
fmt.Println(s.describe())
}