Golang方法重载

34

我有以下内容:

type Base struct {

}

func(base *Base) Get() string {
    return "base"
}

func(base *Base) GetName() string {
    return base.Get()
}

我想定义一个新类型,并使用新的Get()实现,以便可以在原有代码中将该新类型替换Base,并且调用GetName()时可以调用新的Get()实现。如果我使用Java,我会继承Base并重写Get()。那么在Go中,我应该如何实现呢?如果可能的话,我希望避免破坏性更改,这样就不需要更改现有Base的使用者。

我尝试的第一步看起来像是...

type Sub struct {
    Base
}

func(sub *Sub) Get() string {
    return "Sub"
}

...这并不奏效。我的大脑还没有完全适应Go语言。


1
Go语言没有继承,也没有泛型。如果某个东西期望类型为“Base”,你就不能使用其他类型来提供它。 - JimB
1
我想向您推荐这篇文章 - flycash
4个回答

13

接口是一组方法签名的命名集合:
请参见:https://gobyexample.com/interfaces
以及:http://www.golangbootcamp.com/book/interfaces
因此最好不要像面向对象编程那样使用。


你所要求的并不是Golang惯用语法,但是有可能实现(两种方式):

这是你需要的内容(有效的示例代码):

package main

import "fmt"

type Base struct {
}

func (base *Base) Get() string {
    return "base"
}

type Getter interface {
    Get() string
}

func (base *Base) GetName(getter Getter) string {
    if g, ok := getter.(Getter); ok {
        return g.Get()
    } else {
        return base.Get()
    }
}

// user code :
type Sub struct {
    Base
}

func (t *Sub) Get() string {
    return "Sub"
}
func (t *Sub) GetName() string {
    return t.Base.GetName(t)
}

func main() {
    userType := Sub{}
    fmt.Println(userType.GetName()) // user string
}

输出:

Sub

正如您所见,必须在用户代码中进行一次初始化代码:

func (t *Sub) GetName() string {
    return t.Base.GetName(t)
}

此外,这也是有效的:
package main

import "fmt"

type Getter interface {
    Get() string
}

type Base struct {
    Getter
}

func (base *Base) Get() string {
    return "base"
}

func (base *Base) GetName() string {
    return base.Getter.Get()
}

// user code :
type Sub struct {
    Base
}

func (t *Sub) Get() string {
    return "Sub"
}
func New() *Sub {
    userType := &Sub{}
    userType.Getter = interface{}(userType).(Getter)
    return userType
}

func main() {
    userType := New()
    fmt.Println(userType.GetName()) // user string
}

输出:

Sub

正如您所见,用户代码中必须完成一项初始化代码:

userType.Getter = interface{}(userType).(Getter)

47
Go 语言让我哭了好多,几十年来编程语言的进步都被遗忘了。 - Kangur
1
如果我包含一个定义了Base结构体但没有Getter接口的库,而且我无法更改库代码。因此,让我们假设我们没有接口,只有我们想要“覆盖”的Base结构体。是的,我知道,Golang没有“覆盖、多态等”概念,你知道我指的是组合。 - Hermann Schwarz
1
@Kangur,我认为相反,我们从来不需要覆盖方法,而且我们也不知道!我们真正需要的只是一个接口和实现者。没有它确实更好,覆盖方法对机器负担很重,但接口在编译时都被计算出来,程序在运行时不会使用额外的内存来决定调用哪个方法。我们所需要做的就是改变我们的文化,开始更加关注接口,不要使用超类型。 - wellsantos
1
@wellsantos 在写了几个月的 Golang 代码后,我同意你的看法。我认为 Golang 在抽象概念和绑定所有方法编译时间速度之间有着很好的平衡。我现在很喜欢它,但正如你所说,我们必须放弃或调整一些旧的方法。 - Kangur
2
@wellsantos 继承并不“沉重”;它只是一个表中的函数指针。使用具有全有或全无实现的接口的这种方法会导致这种问题,然后不可避免地通过分配函数指针来解决(如此问题已经多次建议)。手动构造函数指针是我们几十年前在C语言中解决这个问题的方法。 - shanef22
显示剩余3条评论

11

Go不是一个传统的面向对象语言:它不知道类和继承的概念。

然而,它包含了非常灵活的接口概念,通过这个概念,可以使许多面向对象的方面得以实现。在Go中,接口提供了一种指定对象行为的方式:如果某个东西能够执行此操作,则它可以在此处使用。

接口定义了一组方法,但这些方法不包含代码:它们没有被实现(这意味着它们是抽象的)。

因此,您可以使用接口来在同一方法中使用不同类型。以下是一个简单的例子来证明:

package main

import (
    "fmt"
)

type Base struct{}
type Baser interface {
    Get() float32
}

type TypeOne struct {
    value float32
    Base
}

type TypeTwo struct {
    value float32
    Base
}

type TypeThree struct {
    value float32
    Base
}

func (t *TypeOne) Get() float32 {
    return t.value
}

func (t *TypeTwo) Get() float32 {
    return t.value * t.value
}

func (t *TypeThree) Get() float32 {
    return t.value + t.value
}

func main() {
    base := Base{}
    t1 := &TypeOne{1, base}
    t2 := &TypeTwo{2, base}
    t3 := &TypeThree{4, base}

    bases := []Baser{Baser(t1), Baser(t2), Baser(t3)}

    for s, _ := range bases {
        switch bases[s].(type) {
        case *TypeOne:
            fmt.Println("TypeOne")
        case *TypeTwo:
            fmt.Println("TypeTwo")
        case *TypeThree:
            fmt.Println("TypeThree")
        }

        fmt.Printf("The value is:  %f\n", bases[s].Get())
    }   
}

Go Playground


29
我不认为这回答了问题。他想要实例化子类、调用GetName并确实调用子类的方法。这个例子没有尝试解决这个问题。 - Rafael Almeida
1
重写方法在哪里?您展示了不同的接口实现。 - S2201
Go语言通过封装其他类型来实现继承的概念。所有字段和方法都暴露在最终类型上,但也可以直接访问。 - Kangur
1
如果Base类没有提供任何字段或方法,为什么要对其进行封装?它可以被删除而没有任何副作用。 - Kangur

7

以下是如何“覆盖”方法并从封装结构体中访问它的方法:

package main

import (
    "fmt"
)

type Getter interface {
    Get() string
}

type Base struct {
}

type Sub1 struct {
    Base // reuse fields and methods
}

type Sub2 struct {
    Base // reuse fields and methods
}

func(base *Base) Get() string {
    return "getBase" 
}

func(sub1 *Sub1) Get() string {
    return "getSub1" // override base method
}


func(sub1 Sub1) CallOverriden() string {
    return sub1.Get()
}

func(sub1 Sub1) CallBase() string {
    return sub1.Base.Get() // access and expose encapsulated/embedded method
}


func main() {
    var g Getter
    g = &Sub1{}
    
    fmt.Println(g.Get()) // getSub1
    
    g = &Sub2{}
    fmt.Println(g.Get()) // getBase // not-overriden method
    
    fmt.Println(Sub1{}.CallOverriden())
    fmt.Println(Sub1{}.CallBase()) // "base" method can be exposed
}

该方法从外部可以正常工作,但从内部无法正常工作,例如对于Base的其他方法调用Get并允许派生/封闭结构来覆盖该行为。 - jiping-s
2
我认为在Golang中这是不可能的@jiping-s。 Base的其他方法说明了它们知道什么-基础其他方法。除非您明确指定,否则它们不知道导出结构。如果您需要使用基础的此方法对不同对象进行操作,那么只需将其作为参数传递即可。func(base * Base)Get(do Doer)string {return do.DoIt()+“基本处理”}。然后,您可以传递任何实现Doer接口的结构类型(实现=具有与Doer相同定义的方法)。 - Kangur

0
对于初来乍到的读者,我不确定这个功能是在哪个版本中引入的,但是这段代码现在可以正常工作(在这里阅读更多here)。
package main

import "fmt"

type Base struct{}

func (b *Base) Get() string {
    return "Base"
}

type Sub struct {
    Base
}

func (s *Sub) Get() string {
    return "Sub!"
}

func main() {
    b := Base{}
    s := &Sub{Base: b}
    fmt.Println(b.Get())
    fmt.Println(s.Get())
}

Output:
Base
Sub!

Program exited.

https://go.dev/play/p/F6OH05LNJfC


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接