BがAから継承し、AのFoo()メソッドのみをオーバーライドするようなコードを実装します。コードがB.Foo()を出力することを望みますが、それでもA.Foo()を出力します。 GolangはC++ではこのように機能しません。動的バインディングが有効になっている場合、コードは希望どおりに機能します。
また、別のコードを投稿しますが、これは機能しますが、実装するのが難しすぎます。ハックのように、Golangスタイルではないと思います。
私の問題は、親のBar()メソッドに何らかのロジックがある場合(たとえば、ファイルを開いてから、いくつかの行を読み取り、Foo()を使用してこれらの行をstdout
とChild(in例は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には実際には継承ではなく、機能のような継承を可能にするメソッドは埋め込みと呼ばれます。
http://golang.org/doc/effective_go.html#embedding
それが基本的に意味することは、埋め込まれた構造体が埋め込まれていることを知らないので、そこから呼び出されるものをオーバーライドすることはできません。実際に埋め込み構造体を取得し、埋め込み構造体からのみ参照を取得できます。
したがって、それを行うための最良の方法は、2番目の例とほぼ同じです。つまり、インターフェイスを使用した何らかの依存性注入です。つまり、Aは実際の作業を行うインターフェイスへの参照を持ちます。たとえば、worker
は、ファイルなどに書き込みます。次に、Bをインスタンス化するときに、Aのworker
を別のワーカーに置き換えます(もちろん、Aを埋め込まなくても実行できます)。 Aは、それがどのワーカーであるかを気にすることなく、myWorker.Work()
のようなことをします。
自分でこれに苦労しています。見つかった2つのソリューション:
慣用的なGo way:「仮想」メソッドを呼び出す共通メソッドを、引数としてインターフェースを持つ外部関数として実装します。
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
2番目のオプションと同様:interface hackery。ただし、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
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()
Goは仮想メソッドのオーバーライドをサポートしていません。したがって、使用するデザインパターンはGoで直接サポートされていません。 A.Bar()の実装を変更すると、A.Foo()がA.Bar()によって呼び出されることを前提とするBなどのすべての派生クラスが破損するため、これは悪い習慣と見なされます。使用するデザインパターンにより、コードが脆弱になります。
Goでそれを行う方法は、Foo()メソッドでFooerインターフェイスを定義することです。 Fooerは、引数としてBar()に渡されるか、Aのフィールドに格納され、A.Bar()によって呼び出されます。 Fooアクションを変更するには、Fooer値を変更します。これはコンポジションと呼ばれ、継承とメソッドのオーバーライドによってFooアクションを変更するよりもはるかに優れています。
Goでやりたいことを行う慣用的な方法を次に示します。 https://play.golang.org/p/jJqXqmNUEHn 。この実装では、Fooerは、インスタンスファクトリ関数NewA()
へのパラメーターによって初期化されるAのメンバーフィールドです。この設計パターンは、Aの存続期間中にFooerが頻繁に変更されない場合に適しています。そうでない場合は、Bar()
メソッドのパラメーターとしてFooerを渡すことができます。
これは、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
最近、私はこれを行う必要があり、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())
}