这里是带有一个接口、一个父结构和 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 中的错误吗?
感谢您的帮助。
实际上,在您的示例中,在
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()
,因为存在这样的方法。
例如以下代码不会打印“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
。