我想实现这样的代码,其中B继承自A并且只覆盖A的Foo()方法,我希望代码打印B.Foo(),但它仍然打印A.Foo(),似乎Golang中的接收器在C ++中不能像这样工作,其中当启用动态绑定时,代码可以像我想要的那样工作。
我还发布了另一段代码,但它实现起来太难了,而且更像是黑客方式,我认为它不是Golang风格。
所以我的问题是:如果父的Bar()方法有一些逻辑,例如,打开一个文件,然后读取一些行,并使用Foo()将这些行输出到stdout
,并使用Child (在示例中为B)想要使用其中的大部分,唯一的区别是Child希望Foo()将行输出到另一个文件。我该如何实施呢?我听说Golang的继承不能像C ++或Java一样工作,Golang中的正确方法是什么?
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()
答案 0 :(得分:16)
正如您所写,Go所具有的并不是真正的继承,允许继承功能的方法称为嵌入。
http://golang.org/doc/effective_go.html#embedding
它的意思基本上是嵌入式结构不知道它是嵌入的,所以你不能覆盖从它调用的任何东西。您实际上可以使用嵌入式结构并仅从嵌入结构中获取它的引用。
所以你最好的方法是或多或少地像你的第二个例子 - 通过使用接口的某种依赖注入。即 - A引用某些接口来执行实际工作,比如worker
,它写入文件或其他任何内容。然后在实例化B时,您还将A worker
替换为另一个工作者(即使没有嵌入A,您也可以这样做)。 A只做myWorker.Work()
这样的事情而不关心它是什么工人。
答案 1 :(得分:3)
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()
答案 2 :(得分:2)
最近我需要这样做,OP提出的合成方法效果很好。
我尝试创建另一个示例来尝试演示父子关系并使其更易于阅读。
https://play.golang.org/p/9EmWhpyjHf:
package main
import (
"fmt"
"log"
)
type FruitType interface {
Wash() FruitType
Eat() string
}
type Fruit struct {
name string
dirty bool
fruit FruitType
}
func (f *Fruit) Wash() FruitType {
f.dirty = false
if f.fruit != nil {
return f.fruit
}
return f
}
func (f *Fruit) Eat() string {
if f.dirty {
return fmt.Sprintf("The %s is dirty, wash it first!", f.name)
}
return fmt.Sprintf("%s is so delicious!", f.name)
}
type Orange struct {
*Fruit
}
func NewOrange() *Orange {
ft := &Orange{&Fruit{"Orange", true, nil}}
ft.fruit = ft
return ft
}
func NewApple() *Fruit {
ft := &Fruit{"apple", true, nil}
return ft
}
func (o *Orange) Eat() string {
return "The orange is so sour!"
}
func main() {
log.Println(NewApple().Eat())
log.Println(NewApple().Wash().Eat())
log.Println(NewOrange().Eat())
log.Println(NewOrange().Wash().Eat())
}
答案 3 :(得分:2)
自己为此奋斗。找到2个解决方案:
惯用的执行方式:将调用“虚拟”方法的通用方法实现为外部函数,并以接口作为参数。
package main
import "fmt"
type ABCD interface {
Foo()
}
type A struct {
}
func (a *A) Foo() {
fmt.Println("A.Foo()")
}
type B struct {
A
}
func (b *B) Foo() {
fmt.Println("B.Foo()")
}
// Bar is common "method", as external function.
func Bar(a ABCD) {
a.Foo()
}
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()
}
在Go Playground上尝试:https://play.golang.org/p/FF4fdvTCRAo
类似于您的第二个选择:界面黑客。但是,由于Bar()并不是特定于A的(它是A和B所共有的),因此让我们将其移至基类,并隐藏实现细节和所有危险的东西:
package main
import "fmt"
//////////////////////////////////////////////////////////////////////
// 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
}
func main() {
b := NewB()
b.Bar() // prints: B.Foo()
a := NewA()
a.Bar() // prints: A.Foo()
}
在Go Playground上尝试:https://play.golang.org/p/0Zcs_arturP
答案 4 :(得分:1)
Go不支持虚拟方法覆盖。因此,Go不直接支持您要使用的设计模式。认为这是不好的做法,因为更改A.Bar()的实现将破坏所有派生类,例如B,假定A.Foo()将由A.Bar()调用。您要使用的设计模式会使代码变脆。
在Go中执行此操作的方法是使用Foo()方法定义Fooer接口。 Fooer将作为参数传递给Bar()或存储在A的字段中,并由A.Bar()调用。要更改Foo动作,请更改Fooer值。这称为合成,它比通过继承和方法重写来更改Foo动作要好得多。
这是一种惯用的方式,可以在Go https://play.golang.org/p/jJqXqmNUEHn中完成您想做的事情。在此实现中,Fooer是A的成员字段,该成员字段由实例工厂函数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:以下是您可能出于文档目的而实现的设计模式的实现:https://play.golang.org/p/HugjIbYbout
答案 5 :(得分:1)
来自C ++ / Python,在其中OOP表现得更好,并且发现go(现在一切都与Web或Web相关,对吗?!),我也迷失了这个问题。我觉得进行中的OOP仅完成了一半。通过嵌入(struct的匿名字段),内部类型的方法是免费提供的,从而引入了继承的思想,后来才了解到局限性。但是,只要在结构中使用嵌入式接口并遵守一些纪律,就可以模拟C ++,例如构造函数,继承,多态性和方法重写。
考虑示例-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
}
中心方法(模板方法)是PrintInfo,它通过定义正确的Area,Perimeter和Name方法来调用任何定义的形状,并按预期工作。例如circle.PrintInfo()将调用circle.Area,circle.Perimeter和circle.Name。
构造函数NewRectangle,NewCircle和NewSquare构造形状对象,基本上分为三个步骤:
初始化结构成员是提高代码重用性的一个明显步骤。例如,Rectangle Init调用基本的PrintableShapeInfo Init方法,而Square Init方法调用基本的Rectangle Init(如前所述,它调用PrintableShapeInfo Init)。
此外,由于嵌入了接口,对象大小仅增加了一点,并带有一对指向方法集和数据区域的指针,如示例输出所示。
我认为代码看起来很不错,唯一需要担心的是,如果专门设置Shape的接口方法集(如NewRectangle,NewCircle和NewSquare函数的情况),则由于代码似乎可以正常工作而会引发一些副作用?!< / p>