methods - Ir al método hijo de llamada de estructura incrustada en lugar del método padre
go parent-child (1)
Aquí una muestra de código Go con una interfaz, una estructura principal y 2 estructuras secundarias
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)
}
}
Tengo para resultados:
$ 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
Mi problema es la llamada de
Shape.PrintArea
que llama
Shape.Area
método
Shape.Area
para Circle y Rectangle en lugar de llamar
Circle.Area
método
Circle.Area
y
Rectangle.Area
.
¿Es esto un error en Go?
Gracias por tu ayuda.
En realidad, en su ejemplo, llamar a
ShapeInterface.PrintArea()
funciona bien en el caso de un
Circle
porque creó un método
PrintArea()
para el tipo
Circle
.
Como no creó un
PrintArea()
para el tipo
Rectangle
, se llamará al método del tipo
Shape
integrado.
Esto no es un error, este es el trabajo previsto.
Go
no
es
(bastante) un lenguaje orientado a objetos
: no tiene clases y
no tiene herencia de tipo
;
pero admite una construcción similar llamada
incrustación
tanto a nivel de
struct
nivel de
interface
, y tiene
methods
.
Lo que espera se llama
métodos virtuales
: espera que el método
PrintArea()
llame al método
Area()
"anulado", pero en Go no hay herencia ni métodos virtuales.
La definición de
Shape.PrintArea()
es llamar a
Shape.Area()
y esto es lo que sucede.
Shape
no sabe qué estructura es y si está incrustado, por lo que no puede "enviar" la llamada al método a un método virtual en tiempo de ejecución.
La
especificación Go Language: los selectores
describen las reglas exactas que se siguen al evaluar una expresión
xf
(donde
f
puede ser un método) para elegir qué método se llamará al final.
Puntos clave:
- Un selector
f
puede denotar un campo o métodof
de un tipoT
, o puede referirse a un campo o métodof
de un campo anónimo anidado deT
El número de campos anónimos atravesados para alcanzarf
se llama profundidad enT
- Para un valor
x
de tipoT
o*T
dondeT
no es un puntero o tipo de interfaz,xf
denota el campo o método en la profundidad más baja enT
donde existe talf
.
Entrando en detalles
Circulo
En el caso de
Circle
:
si.PrintArea()
llamará a
Circle.PrintArea()
porque usted creó dicho método:
func (c *Circle) PrintArea() {
fmt.Printf("%s : Area %v/r/n", c.GetName(), c.Area())
}
En este método,
c.Area()
se llama donde
c
es un
*Circle
, por lo que se llamará al método con el receptor
*Circle
, que también existe.
PrintArea(si)
llama a
si.Area()
.
Como
si
es un
Cicle
y hay un método
Area()
con receptor
Circle
, no se invoca.
Rectángulo
En el caso de
Rectangle
si.PrintArea()
realmente llamará al método
Shape.PrintArea()
porque
no
definió un método
PrintArea()
para el tipo
Rectangle
(no hay ningún método con el receptor
*Rectangle
).
Y la implementación del método
Shape.PrintArea()
llama a
Shape.Area()
no a
Rectangle.Area()
, como se discutió,
Shape
no sabe sobre
Rectangle
.
Entonces verás
Rectangle1 : Area 0
impreso en lugar del
Rectangle1 : Area 20
esperado
Rectangle1 : Area 20
.
Pero si llama a
PrintArea(si)
(pasando el
Rectangle
), llama a
si.Area()
que será
Rectangle.Area()
porque dicho método existe.