英文:
In Go can I use generics to declare the same method to different structs?
问题
有没有办法在golang 1.18的泛型中使用结构体方法?比如像这样,我有一个通用的方法SayName
,可以用于Foo
和Bar
:
package main
import (
"fmt"
)
type Foo struct {
Name string
Number int
FooID string
}
type Bar struct {
Name string
Number int
BarID string
}
func [T Foo|Bar](x *T) SayName() {
fmt.Printf("%d \"%s\"", x.Number, x.Name)
}
func main() {
foo := Foo{Name: "Name 1", Number: 1, FooID: "Foo123"}
foo.SayName()
bar := Bar{Name: "Name 2", Number: 2, BarID: "Bar456"}
bar.SayName()
}
我知道可以通过使用基类型和结构体嵌入或每个类型的接口来以其他方式实现,但这只是一个简单的例子,以保持简单。
更新:为了更清楚,如果我有一个稍微不那么假设的例子,像下面这样。我知道关于结构体嵌入和使用基接口的方法。但是在下面的情况下,如果SayName
被定义为func (b *Base) SayName() {...
,我会得到一个运行时错误,因为Base
类型没有GetID
接口(它将是nil
)。所以我想传入一个具有这个接口的泛型,它将在Foo
和Bar
实例上具有这个接口。我有什么遗漏吗?
例如,如果我为每种类型复制SayName
函数(如注释部分所示),下面的代码将全部工作:
package main
import (
"fmt"
)
type Base struct {
Name string
Number int
}
type GetIDIface interface {
GetID() string
}
type Foo struct {
Base
GetIDIface
FooID string
}
func (f *Foo) GetID() string {
return f.FooID
}
type Bar struct {
Base
GetIDIface
BarID string
}
func (b *Bar) GetID() string {
return b.BarID
}
func [T Foo|Bar](x *T) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s", x.Number, x.Name, x.GetID())
}
/* THIS WORKS
func (x *Foo) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.Number, x.Name, x.GetID())
}
func (x *Bar) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.Number, x.Name, x.GetID())
}*/
func main() {
foo := Foo{Base: Base{Name: "Name 1", Number: 1}, FooID: "Foo123"}
foo.SayName()
bar := Bar{Base: Base{Name: "Name 2", Number: 2}, BarID: "Bar456"}
bar.SayName()
}
英文:
Is there a way to use golang 1.18 generics with struct methods? Ie something like this such that I have a common method SayName
for both Foo
and Bar
:
package main
import (
"fmt"
)
type Foo struct {
Name string
Number int
FooID string
}
type Bar struct {
Name string
Number int
BarID string
}
func [T Foo|Bar](x *T) SayName() {
fmt.Printf("%d \"%s\"", x.Number, x.Name)
}
func main() {
foo := Foo{Name: "Name 1", Number: 1, FooID: "Foo123"}
foo.SayName()
bar := Bar{Name: "Name 2", Number: 2, BarID: "Bar456"}
bar.SayName()
}
I know this can be done in other ways using a base type and struct embedding or interfaces on each, but this is just a contrived example to keep things simple.
UPDATE: To make this more clear, what if I have a slightly less contrived example like below. I know about struct embedding and using base interfaces. But in the case below if SayName
was defined as func (b *Base) SayName() {...
I would get a run time error because the Base
type does not have the GetID
interface (it would be nil
). So I want to pass in a generic which will have this interface on the Foo
and Bar
instances. Am I missing something?
For instance this code below would all work if I duplicate the SayName
function for each type (as seen in the commented out section)
package main
import (
"fmt"
)
type Base struct {
Name string
Number int
}
type GetIDIface interface {
GetID() string
}
type Foo struct {
Base
GetIDIface
FooID string
}
func (f *Foo) GetID() string {
return f.FooID
}
type Bar struct {
Base
GetIDIface
BarID string
}
func (b *Bar) GetID() string {
return b.BarID
}
func [T Foo|Bar](x *T) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s", x.Number, x.Name, x.GetID())
}
/* THIS WORKS
func (x *Foo) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.Number, x.Name, x.GetID())
}
func (x *Bar) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.Number, x.Name, x.GetID())
}*/
func main() {
foo := Foo{Base: Base{Name: "Name 1", Number: 1}, FooID: "Foo123"}
foo.SayName()
bar := Bar{Base: Base{Name: "Name 2", Number: 2}, BarID: "Bar456"}
bar.SayName()
}
答案1
得分: 1
**无聊但更好的解决方案是在每个需要它们的结构体上声明这些方法。**这还使得结构体实现了一个假设的接口,其中包含SayName()
方法,如果需要的话。除此之外,没有语言结构可以在不同的接收器上声明相同的方法。
你可以(但不一定应该)使用一个带有类型约束的通用结构体,限制为Foo
和Bar
,然而你无法访问这些结构体的公共字段。因此,约束条件甚至不能是Foo | Bar
,而是需要方法来访问这些字段。
在你的情况下,你已经有了GetIDIface
,Foo
和Bar
实现了它,所以你可以嵌入它:
type FooBar interface {
Foo | Bar
GetIDIface
}
type Base[T FooBar] struct {
Item T
Name string
Number int
}
func (x *Base[T]) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.Number, x.Name, x.Item.GetID())
}
你可以在playground中看到完整的示例。
然而,这颠倒了Base
和Foo
/Bar
之间的关系。也就是说,Base
不再是一个真正的“基类”;它是一个包装器。如果你选择这条路线,请确保它符合你的应用程序的语义。
另一种选择是使用一个接受具有必要功能的接口的顶层函数。这样就不需要泛型了:
type Base struct {
Name string
Number int
}
// 在基类型本身上添加getter
func (b Base) GetBase() Base {
return b
}
type Foo struct {
Base // 嵌入Base也会提升GetBase()
FooID string
}
// 其他Foo方法
type Bar struct {
Base // 嵌入Base也会提升GetBase()
BarID string
}
// 其他Bar方法
// 具有所需功能的接口
type Sayer interface {
GetBase() Base
GetIDIface
}
func SayName(x Sayer) {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.GetBase().Number, x.GetBase().Name, x.GetID())
}
Playground: https://go.dev/play/p/4RBxW9D53Q8
英文:
The boring and overall better solution is to declare those methods on each struct that needs them. This also makes the structs implement a hypothetical interface with SayName()
method, should you need it. Beyond that, there is no language construct to declare the same method on different receivers.
What you could (but not necessarily should) do, is using a generic struct with a type constraint that restricts to Foo
and Bar
, however you can't access common fields of those structs. So the constraint couldn't even be Foo | Bar
as we'd all like; instead you'd need methods to access those fields.
In your case you already have the GetIDIface
, which Foo
and Bar
implement, so you can embed that:
type FooBar interface {
Foo | Bar
GetIDIface
}
type Base[T FooBar] struct {
Item T
Name string
Number int
}
func (x *Base[T]) SayName() {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.Number, x.Name, x.Item.GetID())
}
You can see the complete example in the playground.
However this reverses the relationship between Base
and Foo
/Bar
. I.e. Base
is not really a "base" anymore; it's a wrapper. Make sure it fits the semantics of your application, if you want to go down this route.
<hr>
An alternative is to use a top level function that takes an interface with the necessary functionality. This way generics aren't needed:
type Base struct {
Name string
Number int
}
// Add getter on the base type itself
func (b Base) GetBase() Base {
return b
}
type Foo struct {
Base // embedding Base promotes GetBase() too
FooID string
}
// other Foo methods
type Bar struct {
Base // embedding Base promotes GetBase() too
BarID string
}
// other Bar methods
// interface with required functionality
type Sayer interface {
GetBase() Base
GetIDIface
}
func SayName(x Sayer) {
fmt.Printf("Number %d \"%s\" with ID of %s\n", x.GetBase().Number, x.GetBase().Name, x.GetID())
}
Playground: https://go.dev/play/p/4RBxW9D53Q8
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论