Я хочу создать структуру, которая будет доступна в других пакетах, но я не хочу позволять изменять эту структуру. В других языках это архивируется, делая все поля закрытыми и открывая только общедоступные геттеры.
Решение с геттерами отлично работает для всех типов данных, кроме срезов и карт, поскольку возвращаемые срезы и карты по умолчанию не копируются, поэтому их можно изменить. Единственное решение, которое мне удалось найти, это создать новую карту/срез и назначить все элементы в цикле, но это приводит к большому количеству повторяющегося и уродливого кода, особенно для больших вложенных структур.
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[]}
}
Есть ли лучший способ инкапсулировать слайсы/карты в Go? Или, может быть, мне вообще не использовать инкапсуляцию в Go и использовать другой подход?
Переход на приемник указателя ничего не изменит. И я беспокоюсь, потому что хочу писать хороший код.
Для срезов (если это то, что вы подразумеваете под массивом) вы можете использовать встроенную функцию copy
. Для карт использование цикла for для создания копии, как вы уже делаете, — это путь. В качестве альтернативы оберните свою карту/срез в структуру, которая предоставляет только геттеры для своего элементы, а не все значение, как вы делаете в своем примере.
«потому что я хочу писать хороший код», вы должны писать хороший код на Go, а не на Java.
@Volker, возможно, геттеры лучше всего реализованы на приемнике значений ... который сказал: 100% согласен с хорошим кодом Go и комментарием кода Java: P
@mkopriva Спасибо за ваш ответ. Если нет других решений, я, вероятно, останусь со своим текущим решением, потому что это упрощает итерацию.
@Volker Вот почему я спросил о разных подходах в конце своего вопроса. Если у вас есть хорошие идеи, буду признателен, если вы что-нибудь о них расскажете. Ответы типа «это плохо, потому что это плохо» не слишком полезны :/
Любой вопрос с пометкой oop & go начинается плохо; Go не является объектно-ориентированным.
@Adrian Вы должны прочитать, что авторы Go говорят об этой теме. Инкапсуляция - это термин из ООП, и авторы Go говорят, что он, по крайней мере, частично объектно-ориентирован, поэтому, на мой взгляд, тег ООП подходит здесь.
Я прочитал много того, что авторы Go сказали по этой теме, хотя без ссылки я понятия не имею, о чем конкретно вы говорите. Go — это не то, что большинство считает объектно-ориентированным. В нем нет классов, объектов, наследования и полиморфизма вне интерфейсов. Типичные шаблоны ООП (например, геттеры/сеттеры) в Go однозначны. Как правило, попытки ООП в Go приводят к разочарованию и низкому качеству.
Я имею в виду FAQ на официальном сайте Go. Вам не нужна поддержка Java, например поддержка языка, для ООП, вы даже можете писать объектно-ориентированный код на C. Об этом было большое обсуждение на SO: stackoverflow.com/questions/351733/…
Я получил несколько отрицательных голосов, я не уверен, что не так с моим вопросом. Он не дублируется, и я думаю, что это общая проблема для новичков. Я был бы признателен за любую обратную связь, из которой я могу извлечь уроки, которые помогут мне улучшить мои вопросы.
Возвращать значения среза или карты — это идиоматично Go. Пользователь вашего пакета будет знать, как эти структуры данных работают в Go.
В вашем примере пользователь 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}]}
Вы должны беспокоиться о таких вещах только в случае параллелизма. То есть, когда несколько горутин обращаются к элементам внутри вашего фрагмента или карты и, возможно, изменяют их.
Так что, может быть, будет лучше удалить все геттеры и просто указать в документации, что эти структуры не должны модифицироваться?
Я думаю, что вы правы. Геттеры и сеттеры не распространены в Go, насколько мне известно. Таким образом, вы можете избавиться от своего геттера, если вам не нужно удовлетворять интерфейс с этой структурой данных. ИМХО, документация необязательна.
Заставьте ваши «геттеры» и «сеттеры» работать с приемником указателя. Или даже лучше: перестаньте волноваться.