Должен ли я инкапсулировать слайс и карты в Go? Если да, то как это сделать?

Я хочу создать структуру, которая будет доступна в других пакетах, но я не хочу позволять изменять эту структуру. В других языках это архивируется, делая все поля закрытыми и открывая только общедоступные геттеры.

Решение с геттерами отлично работает для всех типов данных, кроме срезов и карт, поскольку возвращаемые срезы и карты по умолчанию не копируются, поэтому их можно изменить. Единственное решение, которое мне удалось найти, это создать новую карту/срез и назначить все элементы в цикле, но это приводит к большому количеству повторяющегося и уродливого кода, особенно для больших вложенных структур.

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 и использовать другой подход?

Заставьте ваши «геттеры» и «сеттеры» работать с приемником указателя. Или даже лучше: перестаньте волноваться.

Volker 28.05.2019 14:42

Переход на приемник указателя ничего не изменит. И я беспокоюсь, потому что хочу писать хороший код.

MDobak 28.05.2019 14:45

Для срезов (если это то, что вы подразумеваете под массивом) вы можете использовать встроенную функцию copy. Для карт использование цикла for для создания копии, как вы уже делаете, — это путь. В качестве альтернативы оберните свою карту/срез в структуру, которая предоставляет только геттеры для своего элементы, а не все значение, как вы делаете в своем примере.

mkopriva 28.05.2019 14:49

«потому что я хочу писать хороший код», вы должны писать хороший код на Go, а не на Java.

Volker 28.05.2019 14:56

@Volker, возможно, геттеры лучше всего реализованы на приемнике значений ... который сказал: 100% согласен с хорошим кодом Go и комментарием кода Java: P

Elias Van Ootegem 28.05.2019 14:57

@mkopriva Спасибо за ваш ответ. Если нет других решений, я, вероятно, останусь со своим текущим решением, потому что это упрощает итерацию.

MDobak 28.05.2019 15:14

@Volker Вот почему я спросил о разных подходах в конце своего вопроса. Если у вас есть хорошие идеи, буду признателен, если вы что-нибудь о них расскажете. Ответы типа «это плохо, потому что это плохо» не слишком полезны :/

MDobak 28.05.2019 15:14

Любой вопрос с пометкой oop & go начинается плохо; Go не является объектно-ориентированным.

Adrian 28.05.2019 15:43

@Adrian Вы должны прочитать, что авторы Go говорят об этой теме. Инкапсуляция - это термин из ООП, и авторы Go говорят, что он, по крайней мере, частично объектно-ориентирован, поэтому, на мой взгляд, тег ООП подходит здесь.

MDobak 28.05.2019 15:54

Я прочитал много того, что авторы Go сказали по этой теме, хотя без ссылки я понятия не имею, о чем конкретно вы говорите. Go — это не то, что большинство считает объектно-ориентированным. В нем нет классов, объектов, наследования и полиморфизма вне интерфейсов. Типичные шаблоны ООП (например, геттеры/сеттеры) в Go однозначны. Как правило, попытки ООП в Go приводят к разочарованию и низкому качеству.

Adrian 28.05.2019 16:06

Я имею в виду FAQ на официальном сайте Go. Вам не нужна поддержка Java, например поддержка языка, для ООП, вы даже можете писать объектно-ориентированный код на C. Об этом было большое обсуждение на SO: stackoverflow.com/questions/351733/…

MDobak 28.05.2019 16:14

Я получил несколько отрицательных голосов, я не уверен, что не так с моим вопросом. Он не дублируется, и я думаю, что это общая проблема для новичков. Я был бы признателен за любую обратную связь, из которой я могу извлечь уроки, которые помогут мне улучшить мои вопросы.

MDobak 28.05.2019 16:45
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
Создание API ввода вопросов на разных языках программирования (Python, PHP, Go и Node.js)
API ввода вопросов - это полезный инструмент для интеграции моделей машинного обучения, таких как ChatGPT, в приложения, требующие обработки...
2
12
157
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Возвращать значения среза или карты — это идиоматично 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}]}

Вы должны беспокоиться о таких вещах только в случае параллелизма. То есть, когда несколько горутин обращаются к элементам внутри вашего фрагмента или карты и, возможно, изменяют их.

Так что, может быть, будет лучше удалить все геттеры и просто указать в документации, что эти структуры не должны модифицироваться?

MDobak 28.05.2019 15:21

Я думаю, что вы правы. Геттеры и сеттеры не распространены в Go, насколько мне известно. Таким образом, вы можете избавиться от своего геттера, если вам не нужно удовлетворять интерфейс с этой структурой данных. ИМХО, документация необязательна.

Giulio Micheloni 28.05.2019 15:27

Другие вопросы по теме