是否可以从Golang中的父结构调用重写方法?

时间:2014-01-21 06:56:41

标签: oop inheritance go override

我想实现这样的代码,其中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()

6 个答案:

答案 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个解决方案:

  1. 惯用的执行方式:将调用“虚拟”方法的通用方法实现为外部函数,并以接口作为参数。

    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

  1. 类似于您的第二个选择:界面黑客。但是,由于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构造形状对象,基本上分为三个步骤:

  • 空间分配
  • 多态行为所需的方法集(类似于vtable的C ++)初始化
  • 通过Init方法初始化成员结构

初始化结构成员是提高代码重用性的一个明显步骤。例如,Rectangle Init调用基本的PrintableShapeInfo Init方法,而Square Init方法调用基本的Rectangle Init(如前所述,它调用PrintableShapeInfo Init)。

此外,由于嵌入了接口,对象大小仅增加了一点,并带有一对指向方法集和数据区域的指针,如示例输出所示。

我认为代码看起来很不错,唯一需要担心的是,如果专门设置Shape的接口方法集(如NewRectangle,NewCircle和NewSquare函数的情况),则由于代码似乎可以正常工作而会引发一些副作用?!< / p>