Подтвердить что ты не робот

Можно ли вызвать переопределенный метод из родительской структуры в Golang?

Я хочу реализовать такой код, где B наследуется от A и переопределяет только метод Foo(), и я надеюсь, что код для печати B.Foo(), но он все еще печатает A.Foo(), кажется, что получатель в Golang не может так работать в C++, в котором, когда динамическое связывание включено, код может работать так, как я хочу.

Я также публикую еще один фрагмент кода, который работает, но его слишком сложно реализовать, и, скорее, как хакерский путь, я думаю, что это не стиль Голанга.

Поэтому моя проблема заключается в следующем: если родительский метод Bar() имеет некоторую логику, например, откройте файл, затем прочитайте несколько строк и используйте Foo(), чтобы вывести эти строки в стандартный stdout, а затем в Child (в примере это B) хочет использовать большинство из них, с той лишь разницей, что Child хочет, чтобы Foo() выводила строки в другой файл. Как мне это реализовать? Я слышал, что наследование Голанга не может работать как C++ или Java, и какой правильный путь в Голанге?

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()
4b9b3361

Ответ 1

Как вы писали, то, что Go имеет на самом деле не наследование, метод, который позволяет наследовать подобные функции, называется Embedding.

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

В основном это означает, что встроенная структура не знает, что она встроена, поэтому вы не можете переопределять все, что вызывается из нее. Фактически вы можете взять встроенную структуру и взять ссылку для нее только из структуры вложения.

Таким образом, ваш лучший способ сделать это более или менее похож на ваш второй пример - через какую-то инъекцию зависимости, используя интерфейсы. i.e - A имеет ссылку на некоторый интерфейс, который выполняет фактическую работу, например worker, которая записывает в файл или что-то еще. Затем при создании B вы также заменяете A worker на другого работника (вы можете сделать это даже без вложения A, конечно). A просто делает что-то вроде myWorker.Work(), не заботясь о том, какой он рабочий.

Ответ 2

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

Недавно мне нужно сделать это, и метод композиции, предложенный 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())
}

Ответ 4

Go не поддерживает переопределение виртуальных методов. Таким образом, шаблон проектирования, который вы хотите использовать, напрямую не поддерживается Go. Это считается плохой практикой, потому что изменение реализации A.Bar() нарушит все производные классы, такие как B, которые предполагают, что A.Foo() будет вызываться A.Bar(). Шаблон дизайна, который вы хотите использовать, сделает ваш код ломким.

Способ сделать это в Go - определить интерфейс Fooer с помощью метода Foo(). Fooer будет передан в качестве аргумента Bar() или сохранен в поле A и вызван A.Bar(). Чтобы изменить действие Foo, измените значение Fooer. Это называется составом, и это намного лучше, чем изменение действия Foo путем наследования и переопределения метода.

Вот идиоматический способ сделать то, что вы хотите сделать в Go: https://play.golang.org/p/jJqXqmNUEHn. В этой реализации Fooer является полем-членом A, которое инициализируется параметром функции фабрики экземпляра NewA(). Этот шаблон проектирования предпочтителен, когда Fooer не часто меняется в течение срока службы A. В противном случае вы можете передать Fooer в качестве параметра метода Bar().

Вот как мы изменим поведение Foo() в Go. Это называется композицией, потому что вы изменяете поведение Bar(), изменяя экземпляры, составляющие 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: Вот возможная реализация шаблона проектирования, который вы хотели реализовать для целей документации: https://play.golang.org/p/HugjIbYbout

Ответ 5

Сам боролся с этим. Найдено 2 решения:

  1. Идиоматический путь Go: реализовать общий метод, который вызывает "виртуальный" метод, как внешнюю функцию с интерфейсом в качестве аргумента.

    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 no need to specify values for default-initialized fields.
        Bar(b) // prints: B.Foo()
    }
    

Попробуйте это на игровой площадке Go: https://play.golang.org/p/FF4fdvTCRAo

  1. Похоже на ваш второй вариант: интерфейс взлома. Однако, поскольку 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: https://play.golang.org/p/0Zcs_arturP