-1

I want to create a structure which will be accessible in other packages, but I don't want to allow modify this structure. In other languages this is archived by making all fields private and exposing only public getters.

Solution with getters works fine for all data types except slices and maps because returned slices and maps aren't copied by default so they can be modified. The only solution that I managed to figure out is to create new map/slice and assign all items in a loop but this introduce a lot of repetitive and ugly code, especially for large nested structures.

package main

import (
    "fmt"
)

type OtherStruct struct {
    prop string
}

type Struct struct {
    prop map[string]OtherStruct
}

func (s Struct) Prop() map[string]OtherStruct {
    return s.prop
}

func (s Struct) Prop2() map[string]*OtherStruct {
    prop := make(map[string]*OtherStruct, 0)
    for k := range s.prop {
        v := s.prop[k]
        prop[k] = &v
    }

    return prop
}

func main() {
    var s Struct;

    // Simple getter
    s = Struct{make(map[string]OtherStruct, 0)}
    p1 := s.Prop()
    fmt.Println(s) // &{map[]}
    p1["something"] = OtherStruct{"test"}
    fmt.Println(s) // {map[something:{test}]}

    // Getter which copies map
    s = Struct{make(map[string]OtherStruct, 0)}
    p2 := s.Prop2()
    fmt.Println(s) // &{map[]}
    p2["something"] = &OtherStruct{"test"}
    fmt.Println(s) // &{map[]}
}

Is there any better way to encapsulate slices/maps in Go? Or maybe I shouldn't use encapsulation at all in Go and use different approach?

Adrian
  • 42,911
  • 6
  • 107
  • 99
MDobak
  • 75
  • 5
  • 2
    Make your "getters" and "setters" work on a pointer receiver. Or even better: Stop worrying. – Volker May 28 '19 at 12:42
  • Changing to a pointer receiver won't change anything. And I worry because I want to write good code. – MDobak May 28 '19 at 12:45
  • 2
    For slices (if that's what you mean by array) you can use the builtin `copy` function. For maps using a for loop to create a copy, as you are already doing, is the the way to go. Alternatively wrap your map/slice in a struct that provides only getters for its *elements*, not the whole value as you're doing in your example. – mkopriva May 28 '19 at 12:49
  • 2
    "because I want to write good code" you should write good Go code not good Java code. – Volker May 28 '19 at 12:56
  • @Volker arguably, getters are best implemented on a value receiver... that said: 100% agree on the good Go code vs Java code comment :P – Elias Van Ootegem May 28 '19 at 12:57
  • @mkopriva Thanks for your response. If there are no other solutions I'll probably stuck with my current solution because it makes iterating easier. – MDobak May 28 '19 at 13:14
  • @Volker So this is why I asked about different approaches at the end of my question. If you have some good ideas I will be grateful if you tell something about them. Answers like "it's bad because it's bad" aren't too helpful :/ – MDobak May 28 '19 at 13:14
  • Any question tagged oop & go is off to a bad start; Go is not object-oriented. – Adrian May 28 '19 at 13:43
  • 1
    @Adrian You should read what authors of Go says about this topic. Encapsulation is term from OOP and authors od Go says it is at least partially object oriented so in my opinion OOP tag fits here. – MDobak May 28 '19 at 13:54
  • I have read a lot of what the Go authors have said about the topic, though without a link I have no idea what you're referring to specifically. Go is not what most would consider object-oriented. It has no classes, no objects, no inheritance, and no polymorphism outside of interfaces. Typical OOP patterns (such as getters/setters) are unidiomatic in Go. Generally, attempts at OOP in Go lead to frustration and poor quality. – Adrian May 28 '19 at 14:06
  • I'm referring to the FAQ on the Go official website. You don't need Java like support in the language to do OOP, you can even write object oriented code in C. There was a great discussion about it on SO: https://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c – MDobak May 28 '19 at 14:14
  • 1
    I got few downvotes I'm not sure what's wrong with my question. It's not duplicated and I think it's common problem for newcomers. I'd be grateful for any feedback that I can learn from to help me improve my questions. – MDobak May 28 '19 at 14:45

1 Answers1

1

Returning slice or map values is idiomatic Go. The user of your package will know how those data structures work in Go.

In your example, the user of Struct should know that adding a new entry into the returned map would reflect for any other user of the same map.

// Simple getter
s = Struct{make(map[string]OtherStruct, 0)}
p1 := s.Prop()
fmt.Println(s) // &{map[]}
p1["something"] = OtherStruct{"test"}
fmt.Println(s) // {map[something:{test}]}

You should worry about such things only in case of concurrency. That is, when multiple goroutines are accessing, and possibly changing, the elements inside your slice or map.

Giulio Micheloni
  • 1,290
  • 11
  • 25
  • So maybe it will be better idea to remove all getters and just point in a documentation, that these structures shouldn't be modified? – MDobak May 28 '19 at 13:21
  • I think your are right. Getters and setters are not common in Go, AFAIK. So, you can get rid of your getter, unless you have to satisfy an interface with that data structure. IMHO, documentation is optional. – Giulio Micheloni May 28 '19 at 13:27