问题
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:
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 methodf
of a typeT
, or it may refer to a field or methodf
of a nested anonymous field ofT
. The number of anonymous fields traversed to reachf
is called its depth inT
.- For a value
x
of typeT
or*T
whereT
is not a pointer or interface type,x.f
denotes the field or method at the shallowest depth inT
where there is such anf
.
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 Cicle
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.
来源:https://stackoverflow.com/questions/29390736/go-embedded-struct-call-child-method-instead-parent-method