在Golang中减少代码重复

huangapple go评论80阅读模式
英文:

Reducing code duplication in Golang

问题

我在寻找解决代码重复问题的"Go方式"时遇到了困难。以下是问题的描述:

type (
  WithKey interface {
    key() string
  }

  SharedFunctionality interface {
    WithKey
    MethodA() string
    MethodB() string
    // ... 其他方法 ...
  }

  FirstType struct { ... }
  SecondType struct { ... }
  // ... 其他类型 ...
)

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

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

func runMethodA(k WithKey) string {
  key := k.key()
  // 做一些操作并返回一个字符串
}
func runMethodB(k WithKey) string {
  key := k.key()
  // 做一些其他操作并返回一个字符串
}

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中的M个方法和N个类型,我需要写出M*N个类似上面的一行代码。

我希望能够做到像下面这样:

func (k WithKey) MethodA() string {
  key := k.key()
  // 做一些操作
}

换句话说,我希望在接口类型上定义一个方法。也就是说,所有实现了"WithKey"的对象都会自动获得MethodA() stringMethodB() string等方法,从而自动实现SharedFunctionality接口。类似于Java接口中的默认方法。

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

有没有解决这个问题的"Go方式"呢?

我看到过一种方法,可以创建一个带有接口类型匿名字段的结构体,然后在其中实现方法:

type SharedFuncStruct struct {
  WithKey
}
func (sfs *SharedFuncStruct) MethodA() string {
  key := sfs.key()
  // 其他操作
}
// MethodB()也是类似的实现

然后使用它时可以这样做

```go
first := ... 获取FirstType的值()
sfs := &SharedFuncStruct{first}
sfs.MethodA() // 等等

这看起来似乎可以工作,但仍然感觉有太多的样板代码。

还有其他的替代方案吗?

英文:

I'm having trouble find the "go-way" to solve a code duplication issue. Here's the problem. Consider the following:

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 { ... }

Now, the methods in SharedFunctionality they only depend on the results of the key() method. I could implement them like the following:

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) }

What I dislike about this approach is that, as I add more types (ThirdType, FourthType, etc) or as I add more methods to SharedFunctionality, I have to add tons of boilerplate code... specifically, for M methods in SharedFunctionality, and N types, I would have to spell out M*N one-liners like the 4 above.

What I would love to do is something like:

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

In other words: I'd love to define a method on an Interface type. Meaning: all objects that implement "WithKey" would automatically get MethodA() string, MethodB() string, etc, therefore they would automatically implement the SharedFunctionality interface. Something like default methods in Java interfaces.

However, I know it's impossible to define a method in an Interface type...

What's the go-way of solving this problem?

I've seen an approach where I would create a struct with an anonymous field of the interface type, and then implement the methods there:

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

Then to use it, I would do something like:

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

This looks like it could work, but it still feels like too much boilerplate code.

Any other alternatives?

答案1

得分: 2

看起来你需要提取一个包。我会按照以下方式编写函数:

package keyed

type hasKey interface {
    Key() string
}

func MethodA(k hasKey) string {
    key := k.Key()
    // 其他操作
}

func MethodB(k hasKey) string {
    key := k.Key()
    // 其他操作
}

然后在你的包中:

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)
}

希望这对你有帮助!

英文:

It looks to me like you need to extract a package. The way I would have the function is

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
}

and then

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)
}

答案2

得分: 1

有趣的事实:你可以将接口嵌入到结构体中,然后结构体会自动实现该接口。你可以利用这一点,在接口上有效地定义方法:

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 接口,使用嵌入的 WithKey 返回的任何键。

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

英文:

Fun fact: you can embed interfaces into structs, and the struct then automatically fulfills that interface. You can use this to effectively define methods on interfaces:

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 { ... }

In this case, the KeyHolder struct embeds the WithKey interface, and thus can hold anything that has the key() string method (which both FirstType and SecondType have). You can then define MethodA and MethodB on that struct, and that struct will then fulfill both the WithKey interface (because it embeds it) and the SharedFunctionality interface, using whatever key is returned by the embedded WithKey.

In other words, instead of wrapping FirstType in WithKey and then in SharedFunctionality (meaning FirstType itself has to define key(), MethodA(), and MethodB()), you wrap FirstType in WithKey, then embed it (as a WithKey interface) in some other structure that exists solely to define those default methods MethodA and MethodB, which then fulfills the SharedFunctionality interface.

huangapple
  • 本文由 发表于 2016年7月20日 06:19:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/38469452.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定