Je veux implémenter un tel code, où B hérite de A et substitue seulement la méthode Foo () de A, et j'espère que le code pour imprimer B.Foo (), mais qu'il imprime toujours A.Foo (), il semble que le destinataire Golang ne peut pas fonctionner comme cela en C++, dans lequel, lorsque la liaison dynamique est activée, le code peut fonctionner comme je le souhaite.
Je poste aussi un autre morceau de code, qui fonctionne, mais il est trop difficile à mettre en œuvre, et plutôt comme un hack, je pense que ce n'est pas un style Golang.
Donc, mon problème est le suivant: si la méthode Bar () du parent a une certaine logique, par exemple, ouvrez un fichier, puis lisez quelques lignes et utilisez Foo () pour afficher ces lignes sur stdout, et Child (dans l'exemple, B) veut utiliser la plupart d'entre eux, la seule différence est que l'enfant veut que Foo () affiche les lignes dans un autre fichier. Comment devrais-je le mettre en œuvre? J'ai entendu dire que l'héritage de Golang ne peut pas fonctionner comme C++ ou Java, et quel est le bon chemin dans 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()
la pièce suivante fonctionne, mais quand écrire
a := A{}
a.Bar()
vous rencontrerez une erreur de compilation
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()
Comme vous l'avez écrit, ce qui existe chez Go n'est pas vraiment l'héritage, la méthode qui permet l'héritage comme les fonctionnalités s'appelle Embedding.
http://golang.org/doc/effective_go.html#embedding
En gros, cela signifie que la structure incorporée n'a aucune idée de son imbrication, vous ne pouvez donc pas remplacer tout ce qui est appelé à partir de celle-ci. En réalité, vous ne pouvez utiliser la structure incorporée et utiliser une référence qu'à partir de la structure d'intégration.
Votre meilleure façon de le faire est donc plus ou moins celle de votre deuxième exemple - en utilisant une sorte d'injection de dépendance à l'aide d'interfaces. i.e - A a une référence à une interface qui fait le travail réel, disons worker
, qui écrit dans un fichier ou autre. Ensuite, lors de l’instanciation de B, vous remplacez également la variable worker
de A par un autre ouvrier (vous pouvez le faire même sans incorporer A naturellement). Le A fait juste quelque chose comme myWorker.Work()
sans se soucier de quel travailleur il s'agit.
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()
}
Sortie:
A.Foo()
B.Foo()
Récemment, j’ai eu besoin de le faire et la méthode de composition proposée par OP fonctionne très bien.
J'essaie de créer un autre exemple pour essayer de démontrer la relation parent-enfant et la rendre plus facile à lire.
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())
}
Go ne prend pas en charge le remplacement de méthode virtuelle. Le motif que vous souhaitez utiliser n’est donc pas directement pris en charge par Go. Cela est considéré comme une mauvaise pratique car changer l’implémentation de A.Bar () romprait toutes les classes dérivées telles que B qui supposent que A.Foo () sera appelé par A.Bar (). Le modèle de conception que vous souhaitez utiliser rendra votre code fragile.
La manière de le faire dans Go serait de définir une interface Fooer avec une méthode Foo (). Un Fooer serait passé comme argument à Bar () ou stocké dans un champ de A et appelé par A.Bar (). Pour changer l'action Foo, changez la valeur Fooer. C'est ce qu'on appelle la composition, et c'est bien mieux que de changer l'action de Foo par héritage et méthode surchargée.
Voici une manière idiomatique de faire ce que vous voulez faire dans Go: https://play.golang.org/p/jJqXqmNUEHn . Dans cette implémentation, le Fooer est un champ membre de A qui est initialisé par un paramètre de la fonction de fabrique d'instance NewA()
. Ce modèle de conception est préférable lorsque le Fooer ne change pas fréquemment pendant la durée de vie de A. Sinon, vous pouvez passer le Fooer en tant que paramètre de la méthode Bar()
.
Voici comment nous changeons le comportement de Foo()
dans Go. Il s’appelle composition car vous modifiez le comportement de Bar()
en modifiant les instances composant A.
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: Voici une implémentation possible du modèle de conception que vous souhaitez implémenter à des fins de documentation: https://play.golang.org/p/HugjIbYbout
Lutté avec cela moi-même. 2 solutions trouvées:
Idiomatic Go way : implémente la méthode commune, qui appelle la méthode "virtuelle", en tant que fonction externe avec interface en tant qu'argument.
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()
}
Similaire à votre seconde option: interface hackery . Cependant, comme Bar () n'est pas spécifique à A (il est commun à A et B), déplaçons-le dans la classe de base et masquons les détails d'implémentation et tous les éléments dangereux:
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()
}