Golang:隐式结构匹配

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

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()方法就会被提升,并且RectangleSquare将自动实现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.

huangapple
  • 本文由 发表于 2022年9月2日 19:04:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/73581607.html
匿名

发表评论

匿名网友

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

确定