80 Stimmen

Ist es möglich, die überschriebene Methode aus der übergeordneten Struktur in Golang aufzurufen?

Ich möchte einen solchen Code implementieren, in dem B von A erbt und nur die Methode Foo() von A überschreibt, und ich hoffe, dass der Code B.Foo() ausgibt, aber stattdessen A.Foo() ausgibt. Es scheint, dass der Empfänger in Golang nicht so wie in C++ funktionieren kann, bei dem dynamische Bindung aktiviert ist, kann der Code so funktionieren, wie ich es möchte.

Ich poste auch ein weiteres Stück Code, das funktioniert, aber zu schwer zu implementieren ist und eher wie ein Hack aussieht. Ich denke, dass dies nicht im Golang-Stil ist.

Also mein Problem ist: Wenn die Bar()-Methode des Elternteils einige Logik hat, z. B. eine Datei öffnen, einige Zeilen lesen und Foo() verwenden, um diese Zeilen in stdout auszugeben, und das Kind (im Beispiel B) die meisten davon verwenden möchte, der einzige Unterschied besteht darin, dass das Kind möchte, dass Foo() die Zeilen in eine andere Datei ausgibt. Wie sollte ich das implementieren? Ich habe gehört, dass die Vererbung in Golang nicht wie in C++ oder Java funktionieren kann, und wie ist der richtige Weg in 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()

Das folgende Stück Code funktioniert, aber wenn du

a := A{}
a.Bar()

schreibst, tritt ein Compilerfehler auf

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     // hier arbeitet i wie ein Attribut von b
    b.Bar()

output: B.Foo()

34voto

Not_a_Golfer Punkte 45532

Wie Sie geschrieben haben, ist das, was Go hat, nicht wirklich Vererbung, die Methode, die Vererbungsfunktionen ermöglicht, wird Einbettung genannt.

http://golang.org/doc/effective_go.html#embedding

Im Grunde genommen bedeutet dies, dass die eingebettete Struktur keine Ahnung hat, dass sie eingebettet ist, daher können Sie nichts überschreiben, was von ihr aufgerufen wird. Sie können tatsächlich die eingebettete Struktur nehmen und nur einen Verweis davon aus der einbettenden Struktur nehmen.

Also ist Ihre beste Möglichkeit, dies mehr oder weniger wie Ihr zweites Beispiel zu tun - durch eine Art Abhängigkeitsinjektion unter Verwendung von Schnittstellen. Das bedeutet - A hat einen Verweis auf eine Schnittstelle, die die tatsächliche Arbeit macht, sagen wir worker, der in eine Datei schreibt oder was auch immer. Dann, wenn Sie B instanziieren, ersetzen Sie auch A's worker durch einen anderen Worker (Sie können es sogar ohne Einbettung von A machen). A tut einfach so etwas wie myWorker.Work(), ohne sich darum zu kümmern, welcher Worker es ist.

18voto

metalim Punkte 1360

Ich habe selbst damit gekämpft. Ich habe 2 Lösungen gefunden:

  1. Idiomatischer Go-Weg: Implementieren Sie die gemeinsame "Methode" als externe Funktion mit einem Interface als Argument.

     package main
    
     import "fmt"
    
     // Fooer muss Foo haben
     type Fooer interface {
         Foo()
     }
    
     // Bar ist ein Proxy, der Foo einer spezifischen Instanz aufruft.
     func Bar(a Fooer) {
         a.Foo()
     }
    
     //////////////////////////////////////////////////////////////////////
     // Verwendung
    
     func main() {
         b := &B{} // beachten Sie, dass es ein Zeiger ist
         // es ist auch nicht notwendig, Werte für standardmäßig initialisierte Felder anzugeben.
         Bar(b) // gibt aus: B.Foo()
     }
    
     //////////////////////////////////////////////////////////////////////
     // Implementierung
    
     // A ist eine "Basisklasse"        
     type A struct {
     }
    
     func (a *A) Foo() {
         fmt.Println("A.Foo()")
     }
    
     // B überschreibt Methoden von A
     type B struct {
         A
     }
    
     func (b *B) Foo() {
         fmt.Println("B.Foo()")
     }

Probieren Sie es auf Go Playground aus: https://play.golang.org/p/2TbmHUs9_Dt

  1. Ähnlich wie Ihre zweite Option: Interface-Hackerei. Da Bar() jedoch nicht spezifisch für A ist (es ist für A und B gemeinsam), verschieben wir es in die Basisklasse und verbergen Implementierungsdetails und alles Gefährliche:

     package main
    
     import "fmt"
    
     //////////////////////////////////////////////////////////////////////
     // Verwendung
    
     func main() {
         b := NewB()
         b.Bar() // gibt aus: B.Foo()
    
         a := NewA()
         a.Bar() // gibt aus: A.Foo()
     }
    
     //////////////////////////////////////////////////////////////////////
     // Implementierung.
    
     // aBase ist gemeinsamer "Vorfahre" von A und B.
     type aBase struct {
         ABCD // das Interface einbetten. Da es nur ein Zeiger ist, muss es initialisiert werden!
     }
    
     // Bar ist gemeinsam für A und B.
     func (a *aBase) Bar() {
         a.Foo() // aBase hat keine definierte Foo-Methode, daher ruft sie die Foo-Methode des eingebetteten Interfaces auf.
     }
    
     // Eine Klasse, nicht exportiert
     type a struct {
         aBase
     }
    
     func (a *a) Foo() {
         fmt.Println("A.Foo()")
     }
    
     // B-Klasse, nicht exportiert
     type b struct {
         aBase
     }
    
     func (b *b) Foo() {
         fmt.Println("B.Foo()")
     }
    
     //////////////////////////////////////////////////////////////////////
     // Jetzt öffentliche Funktionen und Methoden.
    
     // ABCD beschreibt alle exportierten Methoden von A und B.
     type ABCD interface {
         Foo()
         Bar()
     }
    
     // NewA gibt ein neues a-Struktur zurück
     func NewA() ABCD {
         a := &a{}
         a.ABCD = a
         return a
     }
    
     // NewB gibt ein neues b-Struktur zurück
     func NewB() ABCD {
         b := &b{}
         b.ABCD = b
         return b
     }

Probieren Sie es auf Go Playground aus: https://play.golang.org/p/0Zcs_arturP

9voto

Nothize T Punkte 137

Kürzlich hatte ich das Bedürfnis, dies zu tun und die von OP vorgeschlagene Kompositionsmethode funktioniert großartig.

Ich versuche ein weiteres Beispiel zu erstellen, um die Eltern-Kind-Beziehung zu demonstrieren und es einfacher zu machen zu lesen.

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("Die %s ist schmutzig, waschen Sie sie zuerst!", f.name)
    }
    return fmt.Sprintf("%s ist so lecker!", 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{"Apfel", true, nil}
    return ft
}

func (o *Orange) Eat() string {
    return "Die Orange ist so sauer!"
}

func main() {
    log.Println(NewApple().Eat())
    log.Println(NewApple().Wash().Eat())
    log.Println(NewOrange().Eat())
    log.Println(NewOrange().Wash().Eat())
}

6voto

chmike Punkte 19151

Go unterstützt kein virtuelles Methoden-Overriding. Das Designmuster, das du verwenden möchtest, wird daher von Go nicht direkt unterstützt. Es gilt als schlechte Praxis, weil eine Änderung der Implementierung von A.Bar() alle abgeleiteten Klassen wie B brechen würde, die davon ausgehen, dass A.Foo() von A.Bar() aufgerufen wird. Das Designmuster, das du verwenden möchtest, macht deinen Code spröde.

Der Weg, es in Go zu tun, wäre, eine Fooer-Schnittstelle mit einer Foo() Methode zu definieren. Ein Fooer würde als Argument an Bar() übergeben oder in einem Feld von A gespeichert und von A.Bar() aufgerufen werden. Um die Foo-Aktion zu ändern, ändere den Fooer-Wert. Das nennt man Komposition, und es ist viel besser als die Änderung der Foo-Aktion durch Vererbung und Methoden-Overriding.

Hier ist eine idiomatische Art, das in Go zu tun, was du tun möchtest: https://play.golang.org/p/jJqXqmNUEHn. In dieser Implementierung ist der Fooer ein Mitgliedsfeld von A, das durch einen Parameter der Instanzfabrikfunktion NewA() initialisiert wird. Dieses Designmuster ist vorzuziehen, wenn der Fooer sich während der Lebensdauer von A nicht häufig ändert. Andernfalls kannst du den Fooer als Parameter der Bar() Methode übergeben.

So ändern wir das Verhalten von Foo() in Go. Es wird Komposition genannt, weil du das Verhalten von Bar() änderst, indem du die Instanzen änderst, die A komponieren.

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: Hier ist eine mögliche Implementierung des Designmusters, das du zu Dokumentationszwecken implementieren wolltest: https://play.golang.org/p/HugjIbYbout

4voto

nordborn Punkte 437
package main

import (
    "fmt"
)

//-- Polymorphism in Arbeit

// Spezifikation von Kindern durch Methodensignaturen
// Hier sollten überschreibbare Methoden definiert werden
type AChildInterface interface {
    Foo()
}

type A struct {
    child AChildInterface
}

//-- /Polymorphismus in Arbeit

// harte A.Bar Methode
func (a *A) Bar() {
    a.child.Foo() // Foo() wird überschrieben = implementiert in einem spezifizierten Kind
}

//-- Standardimplementierungen von änderbaren Methoden

type ADefaults struct{}

func (ad ADefaults) Foo() {
    fmt.Println("A.Foo()")
}

//-- /Standard

//-- spezifiziertes Kind

type B struct {
    ADefaults // Standard-A-Methoden von ADefaults implementieren, in diesem Beispiel nicht erforderlich
}

// überschreibe spezifizierte Methode
func (b B) Foo() {
    fmt.Println("B.Foo()")
}

//-- /spezifiziertes Kind

func main() {
    a := A{ADefaults{}}
    a.Bar()

    // Golang-Style Vererbung = Einbetten des Kindes
    b := A{B{}} // Hinweis: Wir haben ein __Eltern__ mit spezifiziertem __Kind__ erstellt, um das Verhalten zu ändern
    b.Bar()
}

Output:

A.Foo()
B.Foo()

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X