Golang:隐式结构匹配

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

Golang: implicit struct matching

问题

考虑以下代码:

  1. type Rectangle struct {
  2. Width, Height, Area int
  3. }
  4. type Square struct {
  5. Side, Area int
  6. }
  7. type Geometry struct {
  8. Area int
  9. }
  10. func SumGeometries(geometries ...Geometry) (sum int) {
  11. for _, g := range geometries {
  12. sum += g.Area
  13. }
  14. return
  15. }
  16. func TestSumGeometries(t *testing.T) {
  17. rect := Rectangle{5, 4, 20}
  18. square := Square{5, 25}
  19. got := SumGeometries(rect, square) // 无法将rect(Rectangle类型的变量)作为Geometry类型的参数传递给MyFunc,编译器报错
  20. want := 45
  21. if got != want {
  22. t.Error("fail!")
  23. }
  24. }

我希望MyFunc可以接受包含Apple的任何结构体,而不仅仅是特定的BStruct。在Go语言中是否可以实现这一点?

目前我能找到的唯一方法是:

  1. type Rectangle struct {
  2. Width, Height, Area int
  3. }
  4. func (r *Rectangle) GetArea() int {
  5. return r.Area
  6. }
  7. type Square struct {
  8. Side, Area int
  9. }
  10. func (s *Square) GetArea() int {
  11. return s.Area
  12. }
  13. type Areaer interface {
  14. GetArea() int
  15. }
  16. func SumGeometries(geometries ...Areaer) (sum int) {
  17. for _, s := range geometries {
  18. sum += s.GetArea()
  19. }
  20. return
  21. }
  22. func TestArgs(t *testing.T) {
  23. rect := Rectangle{5, 4, 20}
  24. square := Square{5, 25}
  25. got := SumGeometries(&rect, &square) // 无法将rect(Rectangle类型的变量)作为Geometry类型的参数传递给MyFunc,编译器报错
  26. want := 45
  27. if got != want {
  28. t.Error("fail!")
  29. }
  30. }

但这种方式可能不太符合惯用写法:当我已经满足于消费者直接访问数据时,是否需要向结构体中添加一个不必要的方法呢?

英文:

Consider this code:

  1. type Rectangle struct {
  2. Width, Height, Area int
  3. }
  4. type Square struct {
  5. Side, Area int
  6. }
  7. type Geometry struct {
  8. Area int
  9. }
  10. func SumGeometries(geometries ...Geometry) (sum int) {
  11. for _, g := range geometries {
  12. sum += g.Area
  13. }
  14. return
  15. }
  16. func TestSumGeometries(t *testing.T) {
  17. rect := Rectangle{5, 4, 20}
  18. square := Square{5, 25}
  19. got := SumGeometries(rect, square) // cannot use rect (variable of type Rectangle) as Geometry value in argument to MyFunc compilerIncompatibleAssign
  20. want := 45
  21. if got != want {
  22. t.Error("fail!")
  23. }
  24. }

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:

  1. type Rectangle struct {
  2. Width, Height, Area int
  3. }
  4. func (r *Rectangle) GetArea() int {
  5. return r.Area
  6. }
  7. type Square struct {
  8. Side, Area int
  9. }
  10. func (s *Square) GetArea() int {
  11. return s.Area
  12. }
  13. type Areaer interface {
  14. GetArea() int
  15. }
  16. func SumGeometries(geometries ...Areaer) (sum int) {
  17. for _, s := range geometries {
  18. sum += s.GetArea()
  19. }
  20. return
  21. }
  22. func TestArgs(t *testing.T) {
  23. rect := Rectangle{5, 4, 20}
  24. square := Square{5, 25}
  25. got := SumGeometries(&rect, &square) // cannot use rect (variable of type Rectangle) as Geometry value in argument to MyFunc compilerIncompatibleAssign
  26. want := 45
  27. if got != want {
  28. t.Error("fail!")
  29. }
  30. }

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):

  1. type Area struct {
  2. Value int
  3. }
  4. func (a Area) GetArea() int {
  5. return a.Value
  6. }

然后将其嵌入到其他类型中:

  1. type Rectangle struct {
  2. Width, Height int
  3. Area
  4. }
  5. type Square struct {
  6. Side int
  7. Area
  8. }

这样GetArea()方法就会被提升,并且RectangleSquare将自动实现Areaer接口。测试一下:

  1. rect := Rectangle{5, 4, Area{20}}
  2. square := Square{5, Area{25}}
  3. got := SumGeometries(rect, square)
  4. want := 45
  5. if got != want {
  6. fmt.Println("fail!")
  7. }

没有输出(没有错误)。在Go Playground上试一试。

请注意,如果Area只包含一个字段,甚至可以省略包装结构体,直接使用int作为底层类型:

  1. type Area int
  2. func (a Area) GetArea() int {
  3. return int(a)
  4. }

然后使用起来更简单:

  1. rect := Rectangle{5, 4, 20}
  2. 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:

  1. type Area struct {
  2. Value int
  3. }
  4. func (a Area) GetArea() int {
  5. return a.Value
  6. }

And embed this in other types:

  1. type Rectangle struct {
  2. Width, Height int
  3. Area
  4. }
  5. type Square struct {
  6. Side int
  7. Area
  8. }

This way the GetArea() method gets promoted, and Rectangle and Square will automatically implement Areaer. Testing it:

  1. rect := Rectangle{5, 4, Area{20}}
  2. square := Square{5, Area{25}}
  3. got := SumGeometries(rect, square)
  4. want := 45
  5. if got != want {
  6. fmt.Println("fail!")
  7. }

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:

  1. type Area int
  2. func (a Area) GetArea() int {
  3. return int(a)
  4. }

Then using it is simpler:

  1. rect := Rectangle{5, 4, 20}
  2. 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:

确定