Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/vim/5.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Oop 是否可以从Golang中的父结构调用重写的方法?_Oop_Inheritance_Go_Overriding - Fatal编程技术网

Oop 是否可以从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)希望使用其中

我想实现这样的代码,其中B继承了A,只覆盖了Apple的FoE()方法,我希望代码打印B. FoE(),但是它仍然打印A. FoE(),看来Golang的接收器不能在C++中这样工作,当启用动态绑定时,代码可以像我想要的那样工作。p> 我还发布了另一段代码,这很有效,但它太难实现,更像是一种黑客方式,我认为这不是Golang风格

因此,我的问题是:如果父级的Bar()方法有一些逻辑,例如,打开一个文件,然后读取一些行,然后使用Foo()将这些行输出到stdout,而子级(示例中为B)希望使用其中的大部分,唯一的区别是子级希望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()

正如您所写,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()")
     }
    
  • 在Go游乐场试一试:

  • 与第二个选项类似:界面黑客。但是,由于Bar()不是特定于A的(这是A和B的共同点),所以让我们将其移动到基类,并隐藏实现细节和所有危险的内容:

     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方法初始化结构成员
    结构成员初始化是更好的代码重用的独特步骤。例如,Rectangle Init调用基本的PrintableShapeInfo Init方法,而Square Init方法调用基本的Rectangle Init(如前所述,它调用PrintableShapeInfo 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
    }