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

huangapple go评论90阅读模式
英文:

Go embedded struct call child method instead parent method

问题

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

package main

import (
	"fmt"
	"math"
)

// Shape Interface : 定义方法
type ShapeInterface interface {
	Area() float64
	GetName() string
	PrintArea()
}

// Shape Struct : 标准形状,面积为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 : 面积 %v\r\n", s.name, s.Area())
}

// Rectangle Struct : 重新定义面积方法
type Rectangle struct {
	Shape
	w, h float64
}

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

// Circle Struct : 重新定义面积和PrintArea方法
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 : 面积 %v\r\n", c.GetName(), c.Area())
}

// 使用接口的通用PrintArea方法
func PrintArea(s ShapeInterface) {
	fmt.Printf("接口 => %s : 面积 %v\r\n", s.GetName(), s.Area())
}

// 主要指令:每种类型的3个形状
// 将它们存储在ShapeInterface的切片中
// 调用两个方法打印每个形状的面积
func main() {

	s := Shape{name: "形状1"}
	c := Circle{Shape: Shape{name: "圆形1"}, r: 10}
	r := Rectangle{Shape: Shape{name: "矩形1"}, w: 5, h: 4}

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

	for _, si := range listshape {
		si.PrintArea() //!! 问题是调用了哪个Area方法 !!
		PrintArea(si)
	}

}

我得到的结果是:

$ go run essai_interface_struct.go
形状1 : 面积 0
接口 => 形状1 : 面积 0
圆形1 : 面积 314.1592653589793
接口 => 圆形1 : 面积 314.1592653589793
矩形1 : 面积 0
接口 => 矩形1 : 面积 20

我的问题是调用Shape.PrintArea时,它调用了Circle和Rectangle的Shape.Area方法,而不是调用Circle.AreaRectangle.Area方法。

这是Go的一个错误吗?

谢谢你的帮助。

英文:

Here a sample of Go code with an Interface, a Parent Struct and 2 Children Structs

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)
}
}

I have for results :

$ 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

My problem is the call of Shape.PrintArea which call Shape.Area method for Circle and Rectangle instead calling Circle.Area and Rectangle.Area method.

Is this a bug in Go ?

Thanks for your help.

答案1

得分: 8

实际上,在你的例子中,调用ShapeInterface.PrintArea()对于Circle是可以正常工作的,因为你为Circle类型创建了一个PrintArea()方法。由于你没有为Rectangle类型创建一个PrintArea()方法,所以会调用嵌入的Shape类型的方法。

这不是一个错误,这是预期的工作方式。Go语言并不完全是面向对象的语言:它没有类,也没有类型继承;但它支持一种类似的构造,称为_嵌入_,既可以在struct级别上,也可以在interface级别上,并且它确实有方法

你期望的被称为虚方法:你期望PrintArea()方法调用"重写"的Area()方法,但在Go语言中没有继承和虚方法。

Shape.PrintArea()的定义是调用Shape.Area(),这就是发生的情况。Shape不知道它嵌入在哪个结构体中,也不知道它嵌入在哪个结构体中,所以它无法将方法调用"分派"给虚拟的、运行时的方法。

Go语言规范:选择器描述了在评估x.f表达式(其中f可以是一个方法)时遵循的确切规则,以选择最终将调用哪个方法。关键点:

> * 选择器f可以表示类型T的字段或方法f,也可以引用T的嵌套匿名字段的字段或方法f。到达f的匿名字段的数量称为它在T中的深度。
> * 对于类型为T*T的值x,其中T不是指针类型或接口类型,x.f表示在T中最浅深度处的字段或方法,其中存在这样的f

进一步了解

Circle

对于Circlesi.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,并且存在一个具有Circle接收器的方法Area(),所以它会被调用,没有问题。

Rectangle

对于Rectanglesi.PrintArea()实际上会调用Shape.PrintArea()方法,因为你没有Rectangle类型定义一个PrintArea()方法(没有带有*Rectangle接收器的方法)。而且Shape.PrintArea()方法的实现调用的是Shape.Area()而不是Rectangle.Area() - 正如前面讨论的,Shape不知道Rectangle的存在。所以你会看到打印出来的是

Rectangle1 : Area 0

而不是预期的Rectangle1 : Area 20

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

英文:

Actually in your example calling ShapeInterface.PrintArea() works just fine in case of a Circle because you created a PrintArea() method for the type Circle. Since you did not create a PrintArea() for the Rectangle type, the method of the embedded Shape type will be called.

This is not a bug, this is the intended working. Go is 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.

What you expect is called virtual methods: you expect that the PrintArea() method will call the "overridden" Area() method, but in Go there is no inheritance and virtual methods.

The definition of Shape.PrintArea() is to call Shape.Area() and this is what happens. Shape does not know about which struct it is and if it is embedded in, so it can't "dispatch" the method call to a virtual, run-time method.

The Go Language Specification: Selectors describe the exact rules which are followed when evaluating an x.f expression (where f may be a method) to choose which method will be called in the end. Key Points:

> * 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.

Going into details

Circle

In case of Circle: si.PrintArea() will call Circle.PrintArea() because you created such a method:

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

In this method c.Area() is called where c is a *Circle, so the method with *Circle receiver will be called which also exists.

PrintArea(si) calls si.Area(). Since si is a Circle and there is a method Area() with Circle receiver, it is invoked no problem.

Rectangle

In case of Rectangle si.PrintArea() will actually call the method Shape.PrintArea() because you did not define a PrintArea() method for the Rectangle type (there is no method with receiver *Rectangle). And the implementation of Shape.PrintArea() method calls Shape.Area() not Rectangle.Area() - as discussed, Shape doesn't know about Rectangle. So you'll see

Rectangle1 : Area 0

printed instead of the expected Rectangle1 : Area 20.

But if you call PrintArea(si) (passing the Rectangle), it calls si.Area() which will be Rectangle.Area() because such method exists.

huangapple
  • 本文由 发表于 2015年4月1日 20:22:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/29390736.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定