英文:
Sharing method implementations between different structs
问题
假设我们有两个结构体,它们共享一个具有相同名称和目的的属性,但大小不同:
type (
L16 struct {
Length uint16
}
L32 struct {
Length uint32
}
)
目标是使这些结构体具有完全相同的GetLength
方法签名和实现:
func (h *L16) GetLength() int {
return int(h.Length)
}
func (h *L32) GetLength() int {
return int(h.Length)
}
但是要避免为每个结构体重复实现。
所以我尝试了以下方法:
type (
LengthHolder interface {
GetLength() int
}
LengthHolderStruct struct {
LengthHolder
}
L16 struct {
LengthHolderStruct
Length uint16
}
L32 struct {
LengthHolderStruct
Length uint32
}
)
func (h *LengthHolderStruct) GetLength() int {
return int(h.Length)
}
但是会出现错误h.Length undefined (type *LengthHolderStruct has no field or method Length)
。
我们应该如何解决这个问题?
英文:
Say we have 2 structs sharing a property with the same name and purpose, but of different size:
type (
L16 struct {
Length uint16
}
L32 struct {
Length uint32
}
)
The goal is to make those structs have a GetLength
method with exactly the same signature and implementation:
func (h *L16) GetLength() int {
return int(h.Length)
}
func (h *L32) GetLength() int {
return int(h.Length)
}
— but to avoid repeating the implementation for each struct.
So I try:
type (
LengthHolder interface {
GetLength() int
}
LengthHolderStruct struct {
LengthHolder
}
L16 struct {
LengthHolderStruct
Length uint16
}
L32 struct {
LengthHolderStruct
Length uint32
}
)
func (h *LengthHolderStruct) GetLength() int {
return int(h.Length)
}
— but that errors with h.Length undefined (type *LengthHolderStruct has no field or method Length)
.
How do we do it?
答案1
得分: 2
Go 1.17 及以下版本
不太正式的答案是,你不能,也不应该这样做。只需在每个结构体上实现该方法,让未来的你和其他维护者感到满意。
无论如何,假设你绝对必须这样做,当然,嵌入类型对于嵌入类型一无所知,所以你不能从LengthHolderStruct
中引用Length
。
就个人而言,我认为@mh-cbon的回答是一个不错的妥协方案。作为一种替代方案,你可以通过在嵌入的结构体上将Length
字段声明为interface{}
,然后使用类型断言(丢弃了类型安全性)的方式来绕过这个问题。
我不会在我的生产系统中使用以下代码,但是给你参考:
func main() {
l16 := L16{
LengthHolderStruct: LengthHolderStruct{
Length: uint16(200),
// 但是你可以设置为 uint32(200)
},
}
fmt.Println(l16.GetLength())
}
type (
LengthHolder interface {
GetLength() int
}
LengthHolderStruct struct {
Length interface{}
}
L16 struct {
LengthHolderStruct
}
L32 struct {
LengthHolderStruct
}
)
func (h *LengthHolderStruct) GetLength() int {
switch t := h.Length.(type) {
case uint16:
return int(t)
case uint32:
return int(t)
}
return 0
}
<hr>
Go 1.18 及以上版本
使用泛型。只要Constraint
中的类型都可以转换为int
,你可以使用以下代码:
type Constraint interface {
~uint16 | ~uint32
}
type LX[T Constraint] struct {
Length T
}
func (h *LX[T]) GetLength() int {
return int(h.Length)
}
func main() {
lx := LX[uint16]{
Length: uint16(200),
}
fmt.Println(lx.GetLength()) // 200
}
Go Playground: https://go.dev/play/p/LYbDrjQkgCN
英文:
Go 1.17 and below
The unceremonious answer is that <strike>you can't</strike> you shouldn't. Just implement the method on each struct and make the future you and other maintainers happy.
Anyway, let's say that you absolutely must do it, of course the embedded type knows nothing about the embedding type so you can't reference Length
from LengthHolderStruct
.
Personally, I think @mh-cbon answer is a decent compromise. To provide an alternative, you could hack around this in a very ugly way by declaring the Length
field as an interface{}
on the embedded struct and using a type switch (throws type safety in the bin).
I would not use the following code in my production system, but here you go:
func main() {
l16 := L16{
LengthHolderStruct: LengthHolderStruct{
Length: uint16(200),
// but nothing stops you from setting uint32(200)
},
}
fmt.Println(l16.GetLength())
}
type (
LengthHolder interface {
GetLength() int
}
LengthHolderStruct struct {
Length interface{}
}
L16 struct {
LengthHolderStruct
}
L32 struct {
LengthHolderStruct
}
)
func (h *LengthHolderStruct) GetLength() int {
switch t := h.Length.(type) {
case uint16:
return int(t)
case uint32:
return int(t)
}
return 0
}
<hr>
Go 1.18 and above
Use generics. As long as the types in Constraint
can all be converted to int
, you can use the following code:
type Constraint interface {
~uint16 | ~uint32
}
type LX[T Constraint] struct {
Length T
}
func (h *LX[T]) GetLength() int {
return int(h.Length)
}
func main() {
lx := LX[uint16]{
Length: uint16(200),
}
fmt.Println(lx.GetLength()) // 200
}
Go Playground: https://go.dev/play/p/LYbDrjQkgCN
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论