is it possible to call overridden method from parent struct in Golang?

后端 未结 6 1526
攒了一身酷
攒了一身酷 2020-11-27 04:34

I want to implement such code, where B inherit from A and only override A\'s Foo() method, and I hope the code to print B.Foo(), but it still print A.Foo(), it seems that th

6条回答
  •  孤城傲影
    2020-11-27 05:20

    Coming from C++/Python, where OOP is much better represented, and discovering go (now everything is web or web related, right?!) I too stumbled upon this issue. I feel that OOP in go is only half baked. With embedding (struct's anonymous fields), methods of the inner type come along for free, inducing the idea of inheritance, only to learn later on the limitations. However, playing a bit with embedded interfaces within structs and with a bit of discipline, C++ like constructors, inheritance, polymorphism and methods override can be emulated.

    Considering the example - https://play.golang.org/p/6IPi3Mqw8_W

    package main import ( "bytes" "fmt" "log" "math" "unsafe" ) //Emulate C++ like polymorphysm in go, through template method design pattern //========================== Shape interface ============================== //like C++ abstract classes type Shape interface { Area() float32 //Shape's area Perimeter() float32 //Shape's perimeter Name() string //Shape's name (like rectangle, circle, square etc.) } //====================== PrintableShapeInfo ============================= type PrintableShapeInfo struct { Shape //like C++ inheritence, allthow go hasn't such a thing preetyPrintPrefix string } //Init a new PrintableShapeInfo object. The method is disctinct so that it can be called from other contextes as well // //Remark: emulates the C++ constructor init part func (printableShapeInfo *PrintableShapeInfo) Init(preetyPrintPrefix string) { printableShapeInfo.preetyPrintPrefix = preetyPrintPrefix } //The central method which emulates the template method design pattern. It prints some info about a shape by dynamically calling i.e. through pointers) the right methods // //Remark: allthough the design patterns best practices recommend to model a different concept, such as ShapeInfoPrinter, which takes a Shape interface and prints its info, //for the sake of showcasting the template method pattern, the "go's inheritange" like model was chosen func (printableShapeInfo *PrintableShapeInfo) PrintInfo() { log.Println("PrintableShapeInfo::PrintInfo") fmt.Printf("%s PrintableShapeInfo::PrintInfo - %s:\n", printableShapeInfo.preetyPrintPrefix, printableShapeInfo.Name()) //dynamically calls (i.e. through a pointer) a shape's Name method (like Rectangle.Name or Circle.Name or Square.Name) fmt.Printf("\tArea: %f\n", printableShapeInfo.Area()) //dynamically calls (i.e. through a pointer) a shape's Area method (like Rectangle.Area or Circle.Area or Square.Area) fmt.Printf("\tPerimeter: %f\n", printableShapeInfo.Perimeter()) //dynamically calls (i.e. through a pointer) a shape's Perimeter method (like Rectangle.Perimeter or Circle.Perimeter or Square.Perimeter) } //====================== Rectangle ============================= type Rectangle struct { PrintableShapeInfo //like C++ inheritence, allthow go hasn't such a thing width float32 //rectangle's width height float32 //rectangle's heigh } //Creats and init a new rectangle object and properly set it's Shape's interface methors set (similar to C++ class' vtable) // //Remark: emulates the C++ constructor func NewRectangle(width float32, height float32) *Rectangle { log.Println("NewRectangle") rectangle := new(Rectangle) //allocate data rectangle.Shape = rectangle //set the Shape's specific vtable with the Rectangle's methods. Critical for template method pattern rectangle.Init(width, height) //init class return rectangle } //Init a new rectangle object. The method is disctinct so that it can be called from other contextes as well (such as a square Init method. See bellow) // //Remark: emulates the C++ constructor init part func (rectangle *Rectangle) Init(width float32, height float32) { log.Println("Rectangle::Init") //call the base's PrintableShapeInfo struct Init method rectangle.PrintableShapeInfo.Init("###") rectangle.width = width rectangle.height = height } //Compute the rectangle's area func (rectangle *Rectangle) Area() float32 { log.Println("Rectangle::Area") return float32(rectangle.width * rectangle.height) } //Compute the rectangle's perimeter func (rectangle *Rectangle) Perimeter() float32 { log.Println("Rectangle::Perimeter") return float32(2 * (rectangle.width + rectangle.height)) } //Get the rectangle's object name func (rectangle *Rectangle) Name() string { log.Println("Rectangle::Name") return "rectangle" } //====================== Circle ============================= type Circle struct { PrintableShapeInfo //like C++ inheritence, allthow go hasn't such a thing radius float32 //circle's radius } //Creats and init a new circle object and properly set it's Shape's interface methors set (similar to C++ class' vtable) // //Remark: emulates the C++ constructor func NewCircle(radius float32) *Circle { log.Println("NewCircle") circle := new(Circle) //allocate data circle.Shape = circle //set the Shape's specific vtable with the Rectangle's methods. Critical for template method pattern circle.Init(radius) //init class return circle } //Init a new circle object. The method is disctinct so that it can be called from other contextes as well, if needed // //Remark: emulates the C++ constructor init part func (circle *Circle) Init(radius float32) { log.Println("Circle::Init") //call the base's PrintableShapeInfo struct Init method circle.PrintableShapeInfo.Init("ooo") circle.radius = radius } //Compute the circle's area func (circle *Circle) Area() float32 { log.Println("Circle::Area") return math.Pi * float32(circle.radius*circle.radius) } //Compute the circle's perimeter func (circle *Circle) Perimeter() float32 { log.Println("Circle::Perimeter") return 2 * math.Pi * float32(circle.radius) } //Get the circle's object name func (circle *Circle) Name() string { log.Println("Circle::Name") return "circle" } //====================== Rectangle ============================= //Implement Square in terms of Rectangle type Square struct { Rectangle //like C++ inheritence, allthow go hasn't such a thing } //Creats and init a new square object and properly set it's Shape's interface methors set (similar to C++ class' vtable) // //Remark: emulates the C++ constructor init func NewSquare(width float32) *Square { log.Println("NewSquare") square := new(Square) //allocate data square.Shape = square //set the Shape's specific vtable with the Rectangle's methods. Critical for template method pattern square.Init(width) //init class return square } //Init a new square object. The method is disctinct so that it can be called from other contextes as well, if needed // //Remark: emulates the C++ constructor init part func (square *Square) Init(width float32) { log.Println("Square::Init") //since the Rectangle field is anonymous it's nice that we can directly call it's un-overwritten methods but we can still access it, as named Rectangle, along with it's (even overwritten) methods square.Rectangle.Init(width, width) //call Rectangle's init to initialize it's members. Since Square is totaly implemented in Rectangle's terms, there nothing else needed } //Compute the square's area func (square *Square) Area() float32 { log.Println("Square::Area") //since the Rectangle field is anonymous it's nice that we can directly call it's un-overwritten methods but we can still access it, as named Rectangle, along with it's (even overwritten) methods return square.Rectangle.Area() } //Compute the square's perimeter func (square *Square) Perimeter() float32 { log.Println("Square::Perimeter") //since the Rectangle field is anonymous it's nice that we can directly call it's un-overwritten methods but we can still access it, as named Rectangle, along with it's (even overwritten) methods return square.Rectangle.Perimeter() } //Get the square's object name func (square *Square) Name() string { log.Println("Square::Name") return "square" } func main() { //initialize log subsystem so that we can display tham at the main end // bufWriter := bytes.NewBuffer() logStringWriter := bytes.NewBufferString("") log.SetOutput(logStringWriter) rectangle := NewRectangle(2, 3) //create a Rectangle object rectangle.PrintInfo() //should manifest polymorphism behavior by calling Rectangle's Area, Perimeter and Name methods circle := NewCircle(2) //create a Circle object circle.PrintInfo() //should manifest polymorphism behavior by calling Circle's Area, Perimeter and Name methods square := NewSquare(3) //create a Square object square.PrintInfo() //should manifest polymorphism behavior by calling Square's Area, Perimeter and Name methods //print constructs sizes fmt.Printf(` Go constructs sizes: Shape interface size as seen by Rectangle struct: %d `, unsafe.Sizeof(rectangle.Shape)) fmt.Printf("\tRectangle struct size: %d", unsafe.Sizeof(rectangle)) fmt.Printf(` Shape interface size as seen by Circle struct: %d `, unsafe.Sizeof(circle.Shape)) fmt.Printf("\tCircle struct size: %d", unsafe.Sizeof(circle)) fmt.Printf(` Shape interface size as seen by Square struct: %d `, unsafe.Sizeof(square.Shape)) fmt.Printf("\tCircle struct size: %d", unsafe.Sizeof(square)) //print the logs fmt.Println("\n\nDumping traces") fmt.Print(logStringWriter) return }

    The central method (template method) is PrintInfo which, called for any defined shape, works as expected, by calling the right Area, Perimeter and Name methods. Ex. circle.PrintInfo() will call circle.Area, circle.Perimeter and circle.Name.

    The constructor functions, NewRectangle, NewCircle and NewSquare construct shape objects and basically is split in three steps:

    • space allocation
    • methods set (C++ like vtable) init, needed for polymorphic behavior
    • struct members initialization, through Init methods

    The struct member initialization is a distinct step for a better code reuse. For example, Rectangle Init calls the base PrintableShapeInfo Init method while the Square Init method calls the base Rectangle Init (which calls PrintableShapeInfo Init, as said before).

    Also, due to interfaces embedding, the object sizes increases only a bit, with a pair of pointers to the methods set and data area as can be seen in the example output.

    I think the code looks pretty decent and the only concern being if specifically setting the Shape's interface method set (as the case in NewRectangle, NewCircle and NewSquare functions) would trigger some side effects as the code appears to work correctly?!

提交回复
热议问题