donk68254
2015-08-05 15:12 浏览 34
已采纳

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

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[s] = m
        a.updateTotal()
}...

Is there a recommended way of doing this in Go?

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

4条回答 默认 最新

  • 已采纳
    douaoren4402 douaoren4402 2015-08-05 15:17

    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...

    点赞 评论 复制链接分享
  • dongshanxun6479 dongshanxun6479 2015-08-05 15:26

    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
        })
    
    }
    
    点赞 评论 复制链接分享
  • dongzhu7329 dongzhu7329 2015-08-05 15:40

    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)
    }
    
    点赞 评论 复制链接分享
  • dpndp64206 dpndp64206 2015-08-06 05:21

    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.

    点赞 评论 复制链接分享

相关推荐