methods struct go parent-child

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étodo f de un tipo T , o puede referirse a un campo o método f de un campo anónimo anidado de T El número de campos anónimos atravesados ​​para alcanzar f se llama profundidad en T
  • Para un valor x de tipo T o *T donde T no es un puntero o tipo de interfaz, xf denota el campo o método en la profundidad más baja en T donde existe tal f .

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.