嵌入结构调用子方法而不是父方法

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

这里是带有一个接口、一个父结构和 2 个子结构的 Go 代码示例

package main

import (
    "fmt"
    "math"
)

// Shape Interface : defines methods
type ShapeInterface interface {
    Area() float64
    GetName() string
    PrintArea()
}

// Shape Struct : standard shape with an area equal to 0.0
type Shape struct {
    name string
}

func (s *Shape) Area() float64 {
    return 0.0
}

func (s *Shape) GetName() string {
    return s.name
}

func (s *Shape) PrintArea() {
    fmt.Printf("%s : Area %v\r\n", s.name, s.Area())
}

// Rectangle Struct : redefine area method
type Rectangle struct {
    Shape
    w, h float64
}

func (r *Rectangle) Area() float64 {
    return r.w * r.h
}

// Circle Struct : redefine Area and PrintArea method 
type Circle struct {
    Shape
    r float64
}

func (c *Circle) Area() float64 {
    return c.r * c.r * math.Pi
}

func (c *Circle) PrintArea() {
    fmt.Printf("%s : Area %v\r\n", c.GetName(), c.Area())
}

// Genreric PrintArea with Interface
func  PrintArea (s ShapeInterface){
    fmt.Printf("Interface => %s : Area %v\r\n", s.GetName(), s.Area())
}

//Main Instruction : 3 Shapes of each type
//Store them in a Slice of ShapeInterface
//Print for each the area with the call of the 2 methods
func main() {

    s := Shape{name: "Shape1"}
    c := Circle{Shape: Shape{name: "Circle1"}, r: 10}
    r := Rectangle{Shape: Shape{name: "Rectangle1"}, w: 5, h: 4}

    listshape := []c{&s, &c, &r}

    for _, si := range listshape {
        si.PrintArea() //!! Problem is Witch Area method is called !! 
        PrintArea(si)
    }

}

我有结果:

$ go run essai_interface_struct.go
Shape1 : Area 0
Interface => Shape1 : Area 0
Circle1 : Area 314.1592653589793
Interface => Circle1 : Area 314.1592653589793
Rectangle1 : Area 0
Interface => Rectangle1 : Area 20

我的问题是调用

Shape.PrintArea
,它调用圆形和矩形的
Shape.Area
方法,而不是调用
Circle.Area
Rectangle.Area
方法。

这是 Go 中的错误吗?

感谢您的帮助。

methods struct go parent-child
2个回答
8
投票

实际上,在您的示例中,在

ShapeInterface.PrintArea()
的情况下调用
Circle
效果很好,因为您为类型
PrintArea()
创建了一个
Circle
方法。由于您没有为
PrintArea()
类型创建
Rectangle
,因此将调用嵌入的
Shape
类型的方法。

这不是错误,这是预期的工作。 Go (完全)不是一种面向对象的语言:它没有类并且没有类型继承;但它在 struct 级别和

interface
级别上都支持称为
embedding
的类似构造,并且它确实有 methods

你期望的叫做 虚拟方法:你期望

PrintArea()
方法会调用“重写”的
Area()
方法,但 Go 中没有继承和虚拟方法。

Shape.PrintArea()
的定义是调用
Shape.Area()
,这就是发生的情况。
Shape
不知道它是哪个结构体以及它是否嵌入其中,因此它无法将方法调用“分派”到虚拟的运行时方法。

Go 语言规范:选择器 描述了评估 x.f

 表达式(其中 
f
 可能是一个方法)以选择最终调用哪个方法时遵循的确切规则。要点:

    选择器
  • f
     可以表示类型 
    f
     的字段或方法 
    T
    ,也可以指 
    f
     的嵌套 
    匿名字段 的字段或方法 T
    。到达
    f
    所遍历的匿名字段的数量称为
    T中的深度
  • 对于
  • x
    T
    类型的值
    *T
    ,其中
    T
    不是指针或接口类型,
    x.f
    表示
    T
    中最浅深度的字段或方法,其中存在这样一个
    f
进入细节

圆圈

如果是

Circle

si.PrintArea()
 将调用 
Circle.PrintArea()
,因为您创建了这样的方法:

func (c *Circle) PrintArea() { fmt.Printf("%s : Area %v\r\n", c.GetName(), c.Area()) }

在此方法中,会调用

c.Area()

,其中 
c
*Circle
,因此将调用同样存在的带有 
*Circle
 接收器的方法。

PrintArea(si)

致电
si.Area()
。由于 
si
 是一个 
Circle
 并且有一个带有 
Area()
 接收器的方法 
Circle
,因此调用它没有问题。

矩形

Rectangle

 的情况下,
si.PrintArea()
 实际上会调用方法 
Shape.PrintArea()
,因为您 
没有PrintArea()
 类型定义 
Rectangle
 方法(没有带有接收者 
*Rectangle
 的方法)。并且 
Shape.PrintArea()
 方法的实现调用 
Shape.Area()
 
not Rectangle.Area()
 - 正如所讨论的,
Shape
 不知道 
Rectangle
。所以你会看到

Rectangle1 : Area 0

打印而不是预期的

Rectangle1 : Area 20

但是如果你调用

PrintArea(si)

(传递
Rectangle
),它会调用
si.Area()
,这将是
Rectangle.Area()
,因为存在这样的方法。


0
投票
Go 的嵌入语法糖在设计上具有误导性。

例如以下代码不会打印“Child”,而是打印“Parent”:

package main import ( "fmt" ) type Hello interface { Hello() } type Parent struct{} func (this Parent) Hello() { fmt.Printf("%T\n", this) } type Child struct { Parent } func main() { Child{}.Hello() }
代码 

Child{}.Hello()

 看起来好像 
Hello
 的接收者是 
Child
。但事实并非如此!相反,它是一个
Parent

© www.soinside.com 2019 - 2024. All rights reserved.