Go 中的嵌入与继承

问题描述 投票:0回答:3

我正在尝试学习 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

但我的 IDE 告诉我
myCar
有一个
Seats
属性!

由此我明白嵌入与继承不同。

我的问题是;是否有类似于 c++ 的结构继承,其中函数可以采用基本结构,或者这是 Go 处理完全不同的东西?我如何在“Go way”中实现这样的事情?

go
3个回答
6
投票

就像你提到的,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


2
投票

这是我刚开始使用 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 等语言隐式使用它。


0
投票

在 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())
}
© www.soinside.com 2019 - 2024. All rights reserved.