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

Go embedded struct call child method instead parent method

这里是一个带有接口、父结构和 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 的调用,它调用了 Circle 和 Rectangle 的 Shape.Area 方法,而不是调用 Circle.AreaRectangle.Area 方法。

这是 Go 中的错误吗?

感谢您的帮助。

实际上在您的示例中调用 ShapeInterface.PrintArea()Circle 的情况下工作得很好,因为您为类型 Circle 创建了一个 PrintArea() 方法。由于您没有为 Rectangle 类型创建 PrintArea(),因此将调用嵌入 Shape 类型的方法。

这不是错误,这是预期的工作方式。围棋是 not (quite) an object oriented language: it does not have classes and it does not have type inheritance; but it supports a similar construct called embedding both on struct level and on interface level, and it does have methods.

你期望调用的是virtual methods:你期望PrintArea()方法会调用"overridden"Area()方法,但是在Go中没有继承和virtual方法。

Shape.PrintArea()的定义是调用Shape.Area(),这就是发生的事情。 Shape 不知道它是哪个结构以及它是否嵌入其中,因此它不能 "dispatch" 方法调用到虚拟的,运行 时间方法。

Go Language Specification: Selectors 描述了评估 x.f 表达式(其中 f 可能是一个方法)以选择最终调用哪个方法时所遵循的确切规则。要点:

  • A selector f may denote a field or method f of a type T, or it may refer to a field or method f of a nested anonymous field of T. The number of anonymous fields traversed to reach f is called its depth in T.
  • For a value x of type T or *T where T is not a pointer or interface type, x.f denotes the field or method at the shallowest depth in T where there is such an 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() 而不是 Rectangle.Area() - 如前所述,Shape 不知道 Rectangle.所以你会看到

Rectangle1 : Area 0

打印而不是预期的 Rectangle1 : Area 20

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