在 golang 中使用私有地图、切片的最佳实践是啥?

Posted

技术标签:

【中文标题】在 golang 中使用私有地图、切片的最佳实践是啥?【英文标题】:What is the best practice for using private Maps, Slices in golang?在 golang 中使用私有地图、切片的最佳实践是什么? 【发布时间】:2015-10-28 10:59:16 【问题描述】:

我想在地图更新时收到通知,以便我可以重新计算总数。我的第一个想法是保持地图私有,并公开一个 add 方法。这可行,但是我需要能够允许读取和迭代地图(基本上,只读或地图的副本)。我发现发送了地图的副本,但底层数组或数据是相同的,并且实际上由使用“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 中有推荐的方法吗?

【问题讨论】:

【参考方案1】:

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

请注意,映射和切片是描述符。如果您返回一个映射值,它将引用相同的底层数据结构。详情见博文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) // Prints "map[one:1 two:2]", not effected by changes to m2

所以你的GetMailbox() 方法:

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

【讨论】:

假设地图足够小,并且该函数不经常被调用......虽然您的方法的优点是原子性。调用者确实不需要担心地图在迭代时会发生变化。【参考方案2】:

然后你可以拥有私有数据和一个公共迭代器,在每次调用时返回下一个键/值对的副本,不是吗?

与没有与特定数据结构结合的内置结构的语言相比,Go 中的迭代器更少。尽管如此,它们与其他任何地方一样容易制作,并且几乎同样易于使用,除了没有语言语法来范围迭代器这一事实。例如,bufio.Scanner 只是一个迭代器,嫁接了一些方便的东西......

【讨论】:

这听起来对我来说是正确的方法。我会考虑创建一个迭代器,如果我能得到我想要的功能,然后标记它。 你可能对那篇博文感兴趣,它展示了在 Go 中实现迭代器的各种方法的不错探索:ewencp.org/blog/golang-iterators (鉴于您正在处理地图,并牢记性能方面的考虑,您可能希望坚持使用回调的“模式 1”) 谢谢,很棒的文章。正是我需要知道的。【参考方案3】:

您可以使用闭包轻松公开对私有数据的迭代。 如果您想禁用修改,请不要传递 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 := Sm: 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】:

“推荐”方法之一是记录 Mailbox 用户不得修改地图(甚至可能导出地图)。

其他语言提倡编码,例如“如果我只是将大多数东西设为私有和 const,那么我的代码的用户就不会误用我的代码,一切都很好”。我一直认为 Go 哲学更像是“如果我的代码的用户不阅读我的文档或不愿意坚持使用它,那么即使我封装了所有内容,他的代码也会中断。”

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

您会发现这种类型的多个实例“用户不得复制/修改/任何此字段/在 x/等之后。”找到标准库。

【讨论】:

以上是关于在 golang 中使用私有地图、切片的最佳实践是啥?的主要内容,如果未能解决你的问题,请参考以下文章

在多包中使用记录器/配置 Golang 生产的最佳实践

是否有设置 golang 单元测试参数的最佳实践?

C++-Golang的数组类型异同

使用具有不同结构的相同函数的最佳实践 - Golang

ArcGIS发布地图并创建切片缓存方法

亲自实践能够下载的谷歌地图切片url地址谷歌地图数据下载的尝试以及Python爬虫实现