英文:
What is the best practice for using private Maps, Slices in golang?
问题
我想在地图更新时收到通知,以便我可以重新计算总数。我最初的想法是保持地图私有,并公开一个添加方法。这样做是可行的,但是我还需要能够允许地图被读取和迭代(基本上是只读或地图的副本)。我发现的是,发送了地图的副本,但是底层数组或数据是相同的,并且实际上会被使用"getter"的任何人更新。
在Go语言中,有没有推荐的方法来实现这一点?
英文:
I would like to be notified when a map is updated so that I can recalculate the Total. My first thought was to keep the map private, and expose an add method. This works, but then I needed to be able to allow the map to be read and iterated over (Basically, read only or a copy of the map). What I found was that a copy of the map is sent, but the underlying array, or data is the same and actually gets updated by anyone who uses the "getter".
type Account struct{
Name string
total Money
mailbox map[string]Money // I want to make this private but it seems impossible to give read only access - and a public Add method
}
func (a *Account) GetMailbox() map[string]Money{ //people should be able to view this map, but I need to be notified when they edit it.
return a.mailbox
}
func (a *Account) UpdateEnvelope(s string, m Money){
a.mailbox展开收缩 = m
a.updateTotal()
}...
Is there a recommended way of doing this in Go?
答案1
得分: 3
也许最好的方法是返回一个地图的克隆副本(不是地图的值,而是整个地图)。对于切片也是一样。
请注意,地图和切片都是描述符。如果返回地图的值,它将引用相同的底层数据结构。有关详细信息,请参阅博文Go maps in action。
创建一个新地图,复制元素并返回新地图。然后你就不必担心谁修改它。
要克隆地图:
func Clone(m map[string]Money) map[string]Money {
m2 := make(map[string]Money, len(m))
for k, v := range m {
m2[k] = v
}
return m2
}
测试Clone()
函数(在Go Playground上尝试):
m := map[string]Money{"one": 1, "two": 2}
m2 := Clone(m)
m2["one"] = 11
m2["three"] = 3
fmt.Println(m) // 输出"map[one:1 two:2]",不受对m2的更改影响
因此,你的GetMailbox()
方法可以这样实现:
func (a Account) GetMailbox() map[string]Money{
return Clone(a.mailbox)
}
英文:
It may just be better to return a clone of the map (not the map value but everything). Same goes for slices.
Note that maps and slices are descriptors. If you return a map value, it will refer to the same underlying data structures. See blog post Go maps in action for details.
Create a new map, copy the elements and return the new map. Then you don't have to worry about who modifies it.
For making a clone of the map:
func Clone(m map[string]Money) map[string]Money {
m2 := make(map[string]Money, len(m))
for k, v := range m {
m2[k] = v
}
return m2
}
Testing the Clone()
function (try it on the Go Playground):
m := map[string]Money{"one": 1, "two": 2}
m2 := Clone(m)
m2["one"] = 11
m2["three"] = 3
fmt.Println(m) // Prints "map[one:1 two:2]", not effected by changes to m2
And so your GetMailbox()
method:
func (a Account) GetMailbox() map[string]Money{
return Clone(a.mailbox)
}
答案2
得分: 1
然后,你可以拥有私有数据和一个公共迭代器,在每次调用时返回下一个键/值对的副本,对吗?
在Go语言中,迭代器的使用比那些没有与特定数据结构结合的内置构造的语言少。尽管如此,创建迭代器和使用它们与其他语言一样简单,只是没有语言语法来遍历迭代器。例如,bufio.Scanner
只是一个带有一些方便的附加功能的迭代器...
英文:
Then you could have private data and a public iterator that returns a copy of the next key/value pair upon each call, no ?
Iterators are less present in Go than in languages that don't have built-in constructs married to particular data-structures. Nevertheless they're as easy to make as anywhere else, and pretty much as easy to use, save for the fact that there's no language syntax to range an iterator. For example, bufio.Scanner
is just an iterator with a bit of convenience cruft grafted on...
答案3
得分: 1
你可以使用闭包轻松地暴露对私有数据的迭代。如果你想禁用修改操作,只需不传递map参数,而只传递键和值,或者只传递键或只传递值,根据你的需求。
package main
import "fmt"
type M map[string]int
type S struct {
m M
}
func (s *S) Foreach(fn func(M, string, int)) {
for k, v := range s.m {
fn(s.m, k, v)
}
}
func main() {
s := S{m: make(M)}
s.m["xxx"] = 12
s.m["yyy"] = 254
s.m["zzz"] = 138
s.Foreach(func(m M, k string, v int) {
fmt.Println(k, v)
m[k] = v + 1
})
s.Foreach(func(m M, k string, v int) {
fmt.Println(k, v)
m[k] = v + 1
})
}
英文:
You could easily expose iteration over private data with closures.
If you want to disable modification, just don't pass the map parameter, but only keys and values, or only keys or only values, whatever are your needs.
package main
import "fmt"
type M map[string]int
type S struct {
m M
}
func (s *S) Foreach(fn func(M, string, int)) {
for k, v := range s.m {
fn(s.m, k, v)
}
}
func main() {
s := S{m: make(M)}
s.m["xxx"] = 12
s.m["yyy"] = 254
s.m["zzz"] = 138
s.Foreach(func(m M, k string, v int) {
fmt.Println(k, v)
m[k] = v + 1
})
s.Foreach(func(m M, k string, v int) {
fmt.Println(k, v)
m[k] = v + 1
})
}
答案4
得分: 1
其中一种“推荐”的方法是“记录”用户不能修改地图(甚至导出地图)的事实。
其他语言鼓励编码方式可能是“如果我只将大部分内容设为私有和常量,那么我的代码的用户就无法滥用我的代码,一切都会很好”。我一直理解Go的哲学更像是“如果我的代码的用户不阅读我的文档或故意不遵守它,他的代码无论如何都会出错,即使我封装了一切”。
例如,在Go(以及C、Java等语言)中,没有办法指示某些代码是否(或不是)安全用于并发使用:这些都只是文档。除了文档之外,没有任何技术手段可以阻止用户同时调用不安全的并发使用方法或函数。
在标准库中,你会找到几个类似“用户绝不能复制/修改/等等此字段/在x之后”的实例。
英文:
One of the "recommended" ways would be to document that users of Mailbox must not modify the map (possibly even exporting it).
Other languages promote coding like "If I just make most things private and const than users of my code won't be able to miss-use my code and all is fine". I always understood the Go philosophy to be more like "If a user of my code doesn't read my documentation or is willingly not sticking to it his code will break anyway, even if I encapsulate everything."
E.g. in Go (as in C, Java, etc.) there is no way to indicate that some code is (or is not) safe for concurrent use: Such stuff is documentation only. There is nothing which technically prevents a user from concurrently calling unsafe-for-concurrent-use methods or functions except the documentation.
You will find several instances of this type of "Users must not copy/modify/whatever this field at all/after x/etc." ind the std library.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论