【问题标题】:Reducing code duplication in Golang减少 Golang 中的代码重复
【发布时间】:2016-07-19 22:19:59
【问题描述】:

我无法找到解决代码重复问题的“捷径”。这就是问题所在。考虑以下几点:

type (
  WithKey interface {
    key() string
  }

  SharedFunctionality interface {
    WithKey
    MethodA() string
    MethodB() string
    // ... etc ...
  }

  FirstType struct { ... }
  SecondType struct { ... }
  // ... etc ...
)
func (ft *FirstType) key() string { ... }
func (st *SecondType) key() string { ... }

现在,SharedFunctionality 中的方法只依赖于key() 方法的结果。我可以像下面这样实现它们:

func runMethodA(k WithKey) string {
  key := k.key()
  // do something and return a string
}
func runMethodB(k WithKey) string {
  key := k.key()
  // do something else and return a string
}

func (ft *FirstType) MethodA() string { return runMethodA(ft) }
func (ft *FirstType) MethodB() string { return runMethodB(ft) }
func (st *SecondType) MethodA() string { return runMethodA(st) }
func (st *SecondType) MethodB() string { return runMethodB(st) }

我不喜欢这种方法的地方在于,当我添加更多类型(ThirdType、FourthType 等)或向 SharedFunctionality 添加更多方法时,我必须添加大量样板代码……特别是对于SharedFunctionality 和 N 种类型,我必须像上面的 4 一样拼出 M*N 单行。

我会喜欢做的事情是这样的:

func (k WithKey) MethodA() string {
  key := k.key()
  // do something
}

换句话说:我很想在接口类型上定义一个方法。含义:所有实现“WithKey”的对象都会自动得到MethodA() stringMethodB() string等,因此它们会自动实现SharedFunctionality接口。 Java 接口中的默认方法

但是,我知道不可能在接口类型中定义方法...

解决这个问题的方法是什么?

我见过一种方法,我会创建一个带有接口类型的匿名字段的结构,然后在那里实现方法:

type SharedFuncStruct struct {
  WithKey
}
func (sfs *SharedFuncStruct) MethodA() string {
  key := sfs.key()
  // whatever
}
// same for MethodB()

然后要使用它,我会这样做:

first := ... getFirstTypeValue()
sfs := &SharedFuncStruct{first}
sfs.MethodA() // etc

这看起来可行,但仍然感觉样板代码太多。

还有其他选择吗?

【问题讨论】:

  • 您已经大致概述了您的选择。并在下面查看戴夫的答案。想一想:如果一个具体类型除了key() 之外已经有一个MethodA() 方法,会发生什么?至少 嵌入 使得 MethodA() 的含义明确(在“最浅深度”)。
  • 您的问题本质上是“我如何在 go 中执行实现继承和虚拟方法”,但我认为您可能遇到了 xy 问题。 meta.stackexchange.com/questions/66377/what-is-the-xy-problem 。如果你描述一下你正在做的事情的背景,可能有一个不植根于 java 风格的面向对象设计的解决方案更适合 go。

标签: go interface refactoring code-duplication


【解决方案1】:

在我看来,您需要提取一个包。我将拥有该功能的方式是

package keyed

type hasKey interface {
    Key() string
}

func MethodA(k hasKey) string {
    key := k.Key()
    // whatever
}

func MethodB(k hasKey) string {
    key := k.Key()
    // whatever
}

然后

package your_package

import "keyed"

type (
    FirstType struct { ... }
    SecondType struct { ... }
)

func (ft *FirstType) Key() string { ... }
func (st *SecondType) Key() string { ... }

func main() {
    first := &FirstType{}
    second := &SecondType{}
    keyed.MethodA(first)
    keyed.MethodA(second)
    keyed.MethodB(first)
    keyed.MethodB(second)
}

【讨论】:

  • 谢谢!它肯定会减少样板代码的数量。我当然会考虑为某些场景创建包。对于我现在正在使用的特定场景,Kaedys 的建议似乎更好一些——我会接受他的解决方案;但也感谢您的解决方案,非常优雅和有用。 +1!
【解决方案2】:

有趣的事实:您可以将接口嵌入到结构中,然后结构会自动实现该接口。您可以使用它来有效地定义接口上的方法:

https://play.golang.org/p/ZufTOzr9ig

type (
    WithKey interface {
        key() string
    }

    SharedFunctionality interface {
        WithKey
        MethodA() string
        MethodB() string
    }

    KeyHolder struct {
        WithKey
    }

    FirstType struct { ... }
    SecondType struct { ... }
)

func (k *KeyHolder) MethodA() string {
    key := k.key()
    // ...
}

func (k *KeyHolder) MethodB() string {
    key := k.key()
    // ...
}

func NewSharedFunctionality(w WithKey) SharedFunctionality {
    return &KeyHolder{w}
}

func (ft *FirstType) key() string { ... }
func (st *SecondType) key() string { ... }

在这种情况下,KeyHolder 结构嵌入了WithKey 接口,因此可以保存任何具有key() string 方法(FirstTypeSecondType 都有)的东西。然后,您可以在该结构上定义MethodAMethodB,然后该结构将同时满足WithKey 接口(因为它嵌入了它)和SharedFunctionality 接口,使用嵌入式@987654332 返回的任何键@。

换句话说,不是将FirstType 包装在WithKey 中,然后在SharedFunctionality 中(意味着FirstType 本身必须定义key()MethodA()MethodB()),而是包装@987654340 @ 在WithKey 中,然后将其嵌入(作为WithKey 接口)在一些其他结构中,该结构仅用于定义那些默认方法MethodAMethodB,然后实现SharedFunctionality 接口。

【讨论】:

  • 我喜欢这种方法。仍然像我必须输入 more 比我预期的(即,做NewSharedFunctionatily(firstTypeInstance) 这样我就可以打电话给我的.MethodA()),但似乎这是我能得到的最好的。我也喜欢 Dave 的方法,但是,至少目前看来,这个解决方案对我来说效果更好(即,让一个对象实现这些方法,而不是调用一个包上的函数)。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-30
  • 1970-01-01
  • 2013-03-10
  • 1970-01-01
相关资源
最近更新 更多