Oop 是否可以从Golang中的父结构调用重写的方法?
我想实现这样的代码,其中B继承了A,只覆盖了Apple的FoE()方法,我希望代码打印B. FoE(),但是它仍然打印A. FoE(),看来Golang的接收器不能在C++中这样工作,当启用动态绑定时,代码可以像我想要的那样工作。p> 我还发布了另一段代码,这很有效,但它太难实现,更像是一种黑客方式,我认为这不是Golang风格 因此,我的问题是:如果父级的Bar()方法有一些逻辑,例如,打开一个文件,然后读取一些行,然后使用Foo()将这些行输出到stdout,而子级(示例中为B)希望使用其中的大部分,唯一的区别是子级希望Foo()将这些行输出到另一个文件。我应该如何实施它?我听说Golang的继承不能像C++或java那样工作,Golang的正确方法是什么?Oop 是否可以从Golang中的父结构调用重写的方法?,oop,inheritance,go,overriding,Oop,Inheritance,Go,Overriding,我想实现这样的代码,其中B继承了A,只覆盖了Apple的FoE()方法,我希望代码打印B. FoE(),但是它仍然打印A. FoE(),看来Golang的接收器不能在C++中这样工作,当启用动态绑定时,代码可以像我想要的那样工作。p> 我还发布了另一段代码,这很有效,但它太难实现,更像是一种黑客方式,我认为这不是Golang风格 因此,我的问题是:如果父级的Bar()方法有一些逻辑,例如,打开一个文件,然后读取一些行,然后使用Foo()将这些行输出到stdout,而子级(示例中为B)希望使用其中
package main
import (
"fmt"
)
type A struct {
}
func (a *A) Foo() {
fmt.Println("A.Foo()")
}
func (a *A) Bar() {
a.Foo()
}
type B struct {
A
}
func (b *B) Foo() {
fmt.Println("B.Foo()")
}
func main() {
b := B{A: A{}}
b.Bar()
}
output: A.Foo()
下面这段话很管用,但写的时候
a := A{}
a.Bar()
您将遇到编译器错误
package main
import (
"fmt"
)
type I interface {
Foo()
}
type A struct {
i I
}
func (a *A) Foo() {
fmt.Println("A.Foo()")
}
func (a *A) Bar() {
a.i.Foo()
}
type B struct {
A
}
func (b *B) Foo() {
fmt.Println("B.Foo()")
}
func main() {
b := B{A: A{}}
b.i = &b // here i works like an attribute of b
b.Bar()
output: B.Foo()
正如您所写,Go所拥有的并不是真正的继承,允许类似继承的特性的方法称为嵌入 它的基本意思是,嵌入式结构不知道它是嵌入式的,因此不能重写从它调用的任何内容。实际上,您可以获取嵌入式结构,并仅从嵌入式结构获取对它的引用 因此,您最好的方法或多或少类似于您的第二个示例—通过使用接口的某种依赖项注入。i、 e-A引用了一些执行实际工作的接口,比如
worker
,它可以写入文件或任何东西。然后在实例化B时,您还可以用另一个worker替换A的worker
(当然,即使不嵌入A也可以这样做)。A只是执行类似于myWorker.Work()
的操作,而不关心它是什么工作者
package main
import (
"fmt"
)
//-- polymorphism in work
// children specification by methods signatures
// you should define overridable methods here
type AChildInterface interface {
Foo()
}
type A struct {
child AChildInterface
}
//-- /polymorphism in work
// hard A.Bar method
func (a *A) Bar() {
a.child.Foo() // Foo() will be overwritten = implemented in a specified child
}
//-- default implementations of changeable methods
type ADefaults struct{}
func (ad ADefaults) Foo() {
fmt.Println("A.Foo()")
}
//-- /default
//-- specified child
type B struct {
ADefaults // implement default A methods from ADefaults, not necessary in this example
}
// overwrite specified method
func (b B) Foo() {
fmt.Println("B.Foo()")
}
//-- /specified child
func main() {
a := A{ADefaults{}}
a.Bar()
// Golang-style inheritance = embedding child
b := A{B{}} // note: we created __Parent__ with specified __Child__ to change behavior
b.Bar()
}
输出:
A.Foo()
B.Foo()
最近我需要做这件事,OP提出的合成方法非常有效 我尝试创建另一个示例来演示父母和孩子的关系,并使其更易于阅读 :
Go不支持虚拟方法重写。因此,Go不直接支持您要使用的设计模式。这被认为是不好的做法,因为更改A.Bar()的实现会破坏所有派生类,如假定A.Foo()将由A.Bar()调用的B类。您想要使用的设计模式将使代码变得脆弱 在Go中实现这一点的方法是使用Foo()方法定义一个foore接口。Fooer将作为参数传递给Bar(),或存储在的字段中,并由A.Bar()调用。要更改Foo操作,请更改foore值。这称为组合,它比通过继承和方法重写来更改Foo操作要好得多 下面是一个惯用的方法来做你想在围棋中做的事情:。在此实现中,foore是的成员字段,由实例工厂函数
NewA()
的参数初始化。如果Fooer在A的生命周期内不经常更改,则此设计模式更可取。否则,您可以将Fooer作为Bar()
方法的参数传递
这就是我们如何在Go中更改Foo()
的行为。之所以称之为组合,是因为您可以通过更改组成A的实例来更改Bar()
的行为
package main
import (
"fmt"
)
type Fooer interface {
Foo()
}
type A struct {
f Fooer
}
func (a *A) Bar() {
a.f.Foo()
}
func NewA(f Fooer) *A {
return &A{f: f}
}
type B struct {
}
func (b *B) Foo() {
fmt.Println("B.Foo()")
}
type C struct {
}
func (c *C) Foo() {
fmt.Println("C.Foo()")
}
func main() {
a := NewA(new(B))
a.Bar()
a.f = &C{}
a.Bar()
}
PS:以下是您想要实现的设计模式的一个可能实现,用于编写文档:我自己一直在努力解决这个问题。找到了两种解决方案:
package main
import "fmt"
// Fooer has to Foo
type Fooer interface {
Foo()
}
// Bar is a proxy, that calls Foo of specific instance.
func Bar(a Fooer) {
a.Foo()
}
//////////////////////////////////////////////////////////////////////
// usage
func main() {
b := &B{} // note it is a pointer
// also there's no need to specify values for default-initialized fields.
Bar(b) // prints: B.Foo()
}
//////////////////////////////////////////////////////////////////////
// implementation
// A is a "base class"
type A struct {
}
func (a *A) Foo() {
fmt.Println("A.Foo()")
}
// B overrides methods of A
type B struct {
A
}
func (b *B) Foo() {
fmt.Println("B.Foo()")
}
package main
import "fmt"
//////////////////////////////////////////////////////////////////////
// Usage
func main() {
b := NewB()
b.Bar() // prints: B.Foo()
a := NewA()
a.Bar() // prints: A.Foo()
}
//////////////////////////////////////////////////////////////////////
// Implementation.
// aBase is common "ancestor" for A and B.
type aBase struct {
ABCD // embed the interface. As it is just a pointer, it has to be initialized!
}
// Bar is common to A and B.
func (a *aBase) Bar() {
a.Foo() // aBase has no method Foo defined, so it calls Foo method of embedded interface.
}
// a class, not exported
type a struct {
aBase
}
func (a *a) Foo() {
fmt.Println("A.Foo()")
}
// b class, not exported
type b struct {
aBase
}
func (b *b) Foo() {
fmt.Println("B.Foo()")
}
//////////////////////////////////////////////////////////////////////
// Now, public functions and methods.
// ABCD describes all exported methods of A and B.
type ABCD interface {
Foo()
Bar()
}
// NewA returns new struct a
func NewA() ABCD {
a := &a{}
a.ABCD = a
return a
}
// NewB returns new struct b
func NewB() ABCD {
b := &b{}
b.ABCD = b
return b
}
在Go操场上尝试一下:来自C++/Python,在C++/Python中OOP得到了更好的表达,并且发现了Go(现在一切都是web或web相关的,对吧?!)我也偶然发现了这个问题。我觉得围棋中的OOP只是半生不熟。通过嵌入(struct的匿名字段),内部类型的方法免费出现,引入了继承的思想,只是为了稍后了解其局限性。然而,通过在结构中使用嵌入式接口并遵循一定的规则,可以模拟类似于C++的构造函数、继承、多态性和方法重写 考虑到这个例子— 中心方法(模板方法)是PrintInfo,它为任何定义的形状调用,通过调用正确的区域、周长和名称方法按预期工作。例如,circle.PrintInfo()将调用circle.Area、circle.permiture和circle.Name 构造函数NewRectangle、NewCircle和NewSquare构造形状对象,并将它们分为三个步骤:
- 空间分配
- 方法集(类似于vtable的C++)init,多态行为所需
- 通过Init方法初始化结构成员
我认为代码看起来相当不错,唯一需要考虑的是,如果专门设置形状的接口方法集(如NewRectangle、NewCircle和NewSquare函数),会不会触发一些副作用,因为代码似乎工作正常 事实上,第二个例子使用co
package main
import "fmt"
//////////////////////////////////////////////////////////////////////
// Usage
func main() {
b := NewB()
b.Bar() // prints: B.Foo()
a := NewA()
a.Bar() // prints: A.Foo()
}
//////////////////////////////////////////////////////////////////////
// Implementation.
// aBase is common "ancestor" for A and B.
type aBase struct {
ABCD // embed the interface. As it is just a pointer, it has to be initialized!
}
// Bar is common to A and B.
func (a *aBase) Bar() {
a.Foo() // aBase has no method Foo defined, so it calls Foo method of embedded interface.
}
// a class, not exported
type a struct {
aBase
}
func (a *a) Foo() {
fmt.Println("A.Foo()")
}
// b class, not exported
type b struct {
aBase
}
func (b *b) Foo() {
fmt.Println("B.Foo()")
}
//////////////////////////////////////////////////////////////////////
// Now, public functions and methods.
// ABCD describes all exported methods of A and B.
type ABCD interface {
Foo()
Bar()
}
// NewA returns new struct a
func NewA() ABCD {
a := &a{}
a.ABCD = a
return a
}
// NewB returns new struct b
func NewB() ABCD {
b := &b{}
b.ABCD = b
return b
}
package main
import (
"bytes"
"fmt"
"log"
"math"
"unsafe"
)
//Emulate C++ like polymorphism 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++ inheritance, although go has no such a thing
preetyPrintPrefix string
}
//Init a new PrintableShapeInfo object. The method is distinct so that it can be called from other contexts as well
//
//Remark: emulates the C++ constructor init part
func (printableShapeInfo *PrintableShapeInfo) Init(preetyPrintPrefix string) {
printableShapeInfo.preetyPrintPrefix = preetyPrintPrefix
}
//The central method emulates the template method design pattern. It prints some info about a shape by dynamically calling (through pointers) the right methods
//
//Remark: the design patterns best practices recommend to favor composition over inheritance (i.e. model a ShapeInfoPrinter class, 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 (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 (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 (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, although go has no such a thing
width float32 //rectangle's width
height float32 //rectangle's heigh
}
//Creates and init a new rectangle object and properly set its Shape's interface methods 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 distinct so that it can be called from other contexts as well (such as a square Init method. See below)
//
//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, although go has no such a thing
radius float32 //circle's radius
}
//Creates and init a new circle object and properly set its Shape's interface methods 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 distinct so that it can be called from other contexts 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++ inheritance, although go has no such a thing
}
//Creates and init a new square object and properly set its Shape's interface methods 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 distinct so that it can be called from other contexts 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 its un-overwritten methods but we can still access it, as named Rectangle, along with its (even overwritten) methods
square.Rectangle.Init(width, width) //call Rectangle's init to initialize its members. Since Square is 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 them at the main's 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
}