在不同的结构体之间共享方法实现的方法

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

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

huangapple
  • 本文由 发表于 2021年7月23日 19:51:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/68498716.html
匿名

发表评论

匿名网友

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

确定