英文:
Golang: implicit struct matching
问题
考虑以下代码:
type Rectangle struct {
    Width, Height, Area int
}
type Square struct {
    Side, Area int
}
type Geometry struct {
    Area int
}
func SumGeometries(geometries ...Geometry) (sum int) {
    for _, g := range geometries {
        sum += g.Area
    }
    return
}
func TestSumGeometries(t *testing.T) {
    rect := Rectangle{5, 4, 20}
    square := Square{5, 25}
    got := SumGeometries(rect, square) // 无法将rect(Rectangle类型的变量)作为Geometry类型的参数传递给MyFunc,编译器报错
    want := 45
    if got != want {
        t.Error("fail!")
    }
}
我希望MyFunc可以接受包含Apple的任何结构体,而不仅仅是特定的BStruct。在Go语言中是否可以实现这一点?
目前我能找到的唯一方法是:
type Rectangle struct {
    Width, Height, Area int
}
func (r *Rectangle) GetArea() int {
    return r.Area
}
type Square struct {
    Side, Area int
}
func (s *Square) GetArea() int {
    return s.Area
}
type Areaer interface {
    GetArea() int
}
func SumGeometries(geometries ...Areaer) (sum int) {
    for _, s := range geometries {
        sum += s.GetArea()
    }
    return
}
func TestArgs(t *testing.T) {
    rect := Rectangle{5, 4, 20}
    square := Square{5, 25}
    got := SumGeometries(&rect, &square) // 无法将rect(Rectangle类型的变量)作为Geometry类型的参数传递给MyFunc,编译器报错
    want := 45
    if got != want {
        t.Error("fail!")
    }
}
但这种方式可能不太符合惯用写法:当我已经满足于消费者直接访问数据时,是否需要向结构体中添加一个不必要的方法呢?
英文:
Consider this code:
type Rectangle struct {
	Width, Height, Area int
}
type Square struct {
	Side, Area int
}
type Geometry struct {
	Area int
}
func SumGeometries(geometries ...Geometry) (sum int) {
	for _, g := range geometries {
		sum += g.Area
	}
	return
}
func TestSumGeometries(t *testing.T) {
    rect := Rectangle{5, 4, 20}
	square := Square{5, 25}
    got := SumGeometries(rect, square)		// cannot use rect (variable of type Rectangle) as Geometry value in argument to MyFunc compilerIncompatibleAssign
	want := 45
	if got != want {
		t.Error("fail!")
	}
}
I want MyFunc to take whatever struct that contains Apple, not just BStruct in specific.
Is this achievable in Go?
The only way I can find ATM is the following:
type Rectangle struct {
	Width, Height, Area int
}
func (r *Rectangle) GetArea() int {
	return r.Area
}
type Square struct {
	Side, Area int
}
func (s *Square) GetArea() int {
	return s.Area
}
type Areaer interface {
	GetArea() int
}
func SumGeometries(geometries ...Areaer) (sum int) {
	for _, s := range geometries {
		sum += s.GetArea()
	}
	return
}
func TestArgs(t *testing.T) {
    rect := Rectangle{5, 4, 20}
	square := Square{5, 25}
    got := SumGeometries(&rect, &square)		// cannot use rect (variable of type Rectangle) as Geometry value in argument to MyFunc compilerIncompatibleAssign
	want := 45
	if got != want {
		t.Error("fail!")
	}
}
It feels perhaps not idiomatic: would I want to pollute my struct with an unnecessary method when I'd be already happy with consumers accessing the data directly?
答案1
得分: 2
将方法添加到类型中并不会“污染”它。
但是有一种方法可以实现你想要的功能而不重复定义。定义一个Area类型,其中包含具有GetArea()方法的公共字段(在这里是Area):
type Area struct {
    Value int
}
func (a Area) GetArea() int {
    return a.Value
}
然后将其嵌入到其他类型中:
type Rectangle struct {
    Width, Height int
    Area
}
type Square struct {
    Side int
    Area
}
这样GetArea()方法就会被提升,并且Rectangle和Square将自动实现Areaer接口。测试一下:
rect := Rectangle{5, 4, Area{20}}
square := Square{5, Area{25}}
got := SumGeometries(rect, square)
want := 45
if got != want {
    fmt.Println("fail!")
}
没有输出(没有错误)。在Go Playground上试一试。
请注意,如果Area只包含一个字段,甚至可以省略包装结构体,直接使用int作为底层类型:
type Area int
func (a Area) GetArea() int {
    return int(a)
}
然后使用起来更简单:
rect := Rectangle{5, 4, 20}
square := Square{5, 25}
在Go Playground上试一试。
英文:
Adding methods to types it not "polluting".
But there's a way to achieve what you want without repetition. Define an Area type holding the common (here Area) field with GetArea() method:
type Area struct {
	Value int
}
func (a Area) GetArea() int {
	return a.Value
}
And embed this in other types:
type Rectangle struct {
	Width, Height int
	Area
}
type Square struct {
	Side int
	Area
}
This way the GetArea() method gets promoted, and Rectangle and Square will automatically implement Areaer. Testing it:
rect := Rectangle{5, 4, Area{20}}
square := Square{5, Area{25}}
got := SumGeometries(rect, square)
want := 45
if got != want {
	fmt.Println("fail!")
}
Outputs nothing (no error). Try it on the Go Playground.
Note that if Area contains a single field only, you can even omit the wrapper struct and use int as the underlying type directly:
type Area int
func (a Area) GetArea() int {
	return int(a)
}
Then using it is simpler:
rect := Rectangle{5, 4, 20}
square := Square{5, 25}
Try this one on the Go Playground.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。


评论