在 golang 中使用私有地图、切片的最佳做法是什么?

What is the best practice for using private Maps, Slices in golang?

我希望在更新地图时收到通知,以便我可以重新计算总计。我的第一个想法是将地图保密,并公开一个添加方法。这可行,但随后我需要能够读取和迭代地图(基本上,只读或地图的副本)。我发现地图的副本已发送,但底层数组或数据是相同的,并且实际上会被使用 "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[s] = m
        a.updateTotal()
}...

在 Go 中有推荐的方法吗?

然后你可以拥有私人数据和一个 public 迭代器,在每次调用时 returns 下一个 key/value 对的副本,不是吗?

与没有与特定数据结构结合的内置结构的语言相比,迭代器在 Go 中的出现较少。尽管如此,它们与其他任何地方一样易于制作,而且几乎同样易于使用,除了没有语言语法来确定迭代器的范围之外。例如,bufio.Scanner 只是一个迭代器,在...

上嫁接了一些方便的东西

您可以使用闭包轻松公开对私有数据的迭代。 如果你想禁用修改,只要不传map参数,只传keys和values,或者只传keys或只传values,随便你需要。

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

}

return 地图的克隆(不是地图值而是所有内容)可能更好。切片也是如此。

请注意,映射和切片是描述符。如果您 return 一个映射值,它将引用相同的底层数据结构。有关详细信息,请参阅博客 post Go maps in action

创建新地图,复制元素并return新地图。那你就不用担心谁修改了。

制作地图的克隆:

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) // Prints "map[one:1 two:2]", not effected by changes to m2

因此您的 GetMailbox() 方法:

func (a Account) GetMailbox() map[string]Money{
    return Clone(a.mailbox)
}

其中一种 "recommended" 方法是 记录 邮箱用户不得修改地图(甚至可能导出地图)。

其他语言像 "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" 一样提倡编码。我一直理解 Go 哲学更像是 "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."

例如在 Go 中(如在 C 中,Java,等等)没有办法表明某些代码对于并发使用是(或不是)安全的:这些东西只是文档。从技术上讲,没有什么可以阻止用户并发调用不安全的并发使用方法或函数 除了 文档。

您会在标准库中找到这种类型的多个实例 "Users must not copy/modify/whatever this field at all/after x/etc."。