使用函数通过方法来满足接口的要求

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

Using function to satisfy interface via method

问题

我正在尝试编写一个方法,该方法将返回一个满足json.Marshaler接口的函数。我的想法是为结构体提供不同的表示形式。也许我完全错误地处理了这个问题。

func (api *Api) SiteList(c *gin.Context) {
    var sites []db.Site
    if err := api.db.Find(&sites).Error; err != nil {
    }
    var payload []json.Marshaler
    for _, site := range sites {
        payload = append(payload, site.ToApi())
    }
    c.JSON(http.StatusOK, payload)
}

我从这个函数得到的结果是列表中的正确数量的项,但是每个项的值都相同:

[
    {
        "key": "NZ7LCA9HQN3",
        "name": "autumn-waterfall-1573"
    },
    {
        "key": "NZ7LCA9HQN3",
        "name": "autumn-waterfall-1573"
    },
    {
        "key": "NZ7LCA9HQN3",
        "name": "autumn-waterfall-1573"
    },
    {
        "key": "NZ7LCA9HQN3",
        "name": "autumn-waterfall-1573"
    },
    {
        "key": "NZ7LCA9HQN3",
        "name": "autumn-waterfall-1573"
    }
]

最后,这是ToApi的实现:

type EncoderFunc func() ([]byte, error)

func (fn EncoderFunc) MarshalJSON() ([]byte, error) {
    return fn()
}

func (site *Site) ToApi() json.Marshaler {
    return EncoderFunc(func() ([]byte, error) {
        var payload public.Site
        payload.Name = site.Name
        payload.Key = site.Key
        data, err := json.Marshal(payload)
        if err != nil {
            return nil, err
        }
        return data, nil
    })
}
英文:

I am trying to write a method that will return a function that can satisfy the json.Marshaler interface. My reasoning is to provide different representations of the struct. Perhaps I am approaching this completely wrong.

func (api *Api) SiteList(c *gin.Context) {
	var sites []db.Site
	if err := api.db.Find(&sites).Error; err != nil {
	}
	var payload []json.Marshaler
	for _, site := range sites {
		payload = append(payload, site.ToApi())
	}
	c.JSON(http.StatusOK, payload)
}

The result I get from this function is the correct number of items in the list, but the same value for each:

[
    {
        "key": "NZ7LCA9HQN3", 
        "name": "autumn-waterfall-1573"
    }, 
    {
        "key": "NZ7LCA9HQN3", 
        "name": "autumn-waterfall-1573"
    }, 
    {
        "key": "NZ7LCA9HQN3", 
        "name": "autumn-waterfall-1573"
    }, 
    {
        "key": "NZ7LCA9HQN3", 
        "name": "autumn-waterfall-1573"
    }, 
    {
        "key": "NZ7LCA9HQN3", 
        "name": "autumn-waterfall-1573"
    }
]

Finally, here is the ToApi implementation:

type EncoderFunc func() ([]byte, error)

func (fn EncoderFunc) MarshalJSON() ([]byte, error) {
	return fn()
}

func (site *Site) ToApi() json.Marshaler {
	return EncoderFunc(func() ([]byte, error) {
		var payload public.Site
		payload.Name = site.Name
		payload.Key = site.Key
		data, err := json.Marshal(payload)
		if err != nil {
			return nil, err
		}
		return data, nil
	})
}

答案1

得分: 5

这似乎是一个经典的闭包陷阱。有一个相关的FAQ部分。

基本上,在你的for循环中,site每次都有相同的地址。所有的函数都是在这个地址上闭包的。所以当你在for循环之后评估它时,它将重复在相同(最后一个)地址上调用MarshalJSON。你可以通过在每次迭代时创建一个新值来纠正这个问题:

for _, site := range sites {
    site := site // 完全合法和惯用的写法。
    payload = append(payload, site.ToApi())
}

Playground: https://play.golang.org/p/eFujC1hEyD

另一个相关的文档片段来自Effective Go

这个bug在Go的for循环中,循环变量在每次迭代中被重用,所以req变量在所有goroutine之间是共享的。这不是我们想要的。(...) 另一个解决方案是只需创建一个同名的新变量,就像这个例子中一样:

for req := range queue {
    req := req // 为goroutine创建req的新实例。
    sem <- 1
    go func() {
        process(req)
        <-sem
    }()
}

这个部分是关于goroutines的,但显然也适用于所有的闭包。

英文:

This seems like a classic closure gotcha. There is a related FAQ section.

Basically, site in your for-loop has the same address every time. All your functions are closed over this address. So when you evaluate it after the for-loop, it will repeatedly call MarshalJSON on the value on the same (last) address. You can correct that by creating a new value on every iteration:

for _, site := range sites {
    site := site // Perfectly legal and idiomatic.
    payload = append(payload, site.ToApi())
}

Playground: https://play.golang.org/p/eFujC1hEyD

Another related piece of documentation from Effective Go:

>The bug is that in a Go for loop, the loop variable is reused for each iteration, so the req variable is shared across all goroutines. That's not what we want. (...) Another solution is just to create a new variable with the same name, as in this example:
>
for req := range queue {
req := req // Create new instance of req for the goroutine.
sem <- 1
go func() {
process(req)
<-sem
}()
}

This section is about goroutines, but apparently this also applies to all closures.

huangapple
  • 本文由 发表于 2015年2月24日 23:08:11
  • 转载请务必保留本文链接:https://go.coder-hub.com/28699283.html
匿名

发表评论

匿名网友

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

确定