web-dev-qa-db-ja.com

Golangの親構造体からオーバーライドされたメソッドを呼び出すことは可能ですか?

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()
46
zhaozhi

あなたが書いたように、Goには実際には継承ではなく、機能のような継承を可能にするメソッドは埋め込みと呼ばれます。

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

それが基本的に意味することは、埋め込まれた構造体が埋め込まれていることを知らないので、そこから呼び出されるものをオーバーライドすることはできません。実際に埋め込み構造体を取得し、埋め込み構造体からのみ参照を取得できます。

したがって、それを行うための最良の方法は、2番目の例とほぼ同じです。つまり、インターフェイスを使用した何らかの依存性注入です。つまり、Aは実際の作業を行うインターフェイスへの参照を持ちます。たとえば、workerは、ファイルなどに書き込みます。次に、Bをインスタンス化するときに、Aのworkerを別のワーカーに置き換えます(もちろん、Aを埋め込まなくても実行できます)。 Aは、それがどのワーカーであるかを気にすることなく、myWorker.Work()のようなことをします。

18
Not_a_Golfer

自分でこれに苦労しています。見つかった2つのソリューション:

  1. 慣用的な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

  1. 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

4
metalim
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()
3
nordborn

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

2
chmike

最近、私はこれを行う必要があり、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())
}
2
Nothize T