Структуры в Go
Теория: Методы у структур. Инкапсуляция и экспортируемость
Введение
Когда мы работаем с данными в программах, почти всегда важно не только хранить их, но и выполнять над ними действия. Представим банковский счет: мало знать, кто владелец и какой у него баланс — нам нужно уметь пополнить счет, снять деньги, проверить баланс, а может, еще и рассчитать проценты.
Если просто хранить данные в структуре, то все это поведение придется реализовывать отдельными функциями, куда мы будем каждый раз передавать структуру как аргумент. Такой код быстро становится громоздким: программисту приходится помнить, какую именно функцию вызвать и в каком порядке передавать аргументы.
Go предлагает более удобный путь: методы у структур. Они позволяют связать данные и поведение в единое целое. Структура отвечает за хранение состояния, а методы описывают, что с этим состоянием можно делать. Получается более логичный и предсказуемый код.
Первые шаги: методы как функции у объекта
В самом начале у нас есть простая структура User, которая хранит имя и email пользователя:
Представим, что нам нужно поприветствовать пользователя по имени. Мы могли бы написать обычную функцию:
Вызов выглядел бы так:
Вроде все работает, но получается не очень удобно: мы должны помнить, что у нас есть какая-то функция Greet, и каждый раз явно передавать туда user.
В Go есть более естественный способ — сделать метод у самой структуры User:
Теперь вызов выглядит так, словно это действие принадлежит самому объекту:
Как выглядит синтаксис метода в Go
В Go метод — это не отдельная сущность языка, как, например, в Java или C#. Здесь нет ключевого слова method или конструкции class. Все устроено проще: метод объявляется тем же ключевым словом func, что и обычная функция.
Разница только в одном: у метода есть получатель (receiver). Он пишется в круглых скобках перед именем функции и указывает, к какому типу этот метод относится.
Общий шаблон:
или, если метод должен изменять данные:
Здесь:
r— имя переменной-получателя (обычно короткое, какuдляUser, a дляAccount).ReceiverType— тип, к которому метод прикреплен (чаще всего структура).MethodName— имя метода, которое затем можно вызвать у объекта.
Сравним функцию и метод
Функция:
Метод:
Теперь можно вызвать так:
Метод и функция внутри устроены одинаково. Главное отличие — в наличии (receiver) перед именем. Именно это связывает поведение с типом.
Поэтому можно сказать, что метод в Go — это обычная функция + получатель.
Метод и функция внутри одинаковые, но синтаксис позволяет нам вызывать приветствие у объекта напрямую. Это делает код логичнее: теперь у пользователя есть действие «поприветствовать».
Методы, изменяющие состояние
В реальных программах методы почти всегда что-то меняют. Например, давайте сделаем структуру банковского счета:
Если мы хотим реализовать метод для пополнения счета, нам нужно, чтобы он менял поле Balance. Для этого в Go используют указатель-получатель:
Теперь в коде:
Если бы мы написали (a Account) вместо (a *Account), то метод изменял бы только копию объекта, и исходный баланс остался бы прежним.
Проверка условий: инкапсуляция логики
Вариант «на коленке» для снятия денег выглядел бы так:
Но это опасно: если на счету меньше денег, получится отрицательный баланс.
Правильнее спрятать проверку внутрь метода, чтобы внешний код был простым и безопасным:
Теперь в коде все аккуратно:
Внешнему коду не нужно знать, что внутри метода происходит проверка — это скрытая деталь реализации. Такой прием называется инкапсуляция: мы оставляем наружу только удобные действия, а внутренние проверки и ограничения прячем.
Экспортируемость и приватные методы
В Go нет ключевых слов public или private, как в других языках. Все решается очень просто:
- если имя начинается с заглавной буквы — оно видно из других пакетов;
- если со строчной — оно доступно только внутри пакета.
Пример:
Таким образом, мы можем оставлять служебные методы «под капотом», а наружу показывать только удобный и безопасный интерфейс.
Более сложный пример: интернет-магазин
Теперь давай посмотрим на пример поближе к реальной системе. Допустим, у нас есть заказы в интернет-магазине:
Что можно делать с заказом? Добавлять товары и менять статус. Запишем это методами:
Использование будет выглядеть так:
Вместо того чтобы вручную возиться с полями, внешний код использует методы — и это сильно повышает читаемость и надежность. Заказ становится полноценным объектом со своим поведением.
В Go нет жесткого ограничения на количество методов у структуры. Их может быть столько, сколько нужно для логики программы.
На практике все зависит от роли структуры. Если это простая вспомогательная сущность вроде Point с координатами, у нее может быть всего один-два метода, например, для вычисления расстояния или сдвига точки. Если же структура отражает крупный объект из предметной области — например, Order в интернет-магазине или User в системе авторизации, — у нее могут быть десятки методов: добавление и удаление элементов, смена статуса, проверка условий, работа с историей.
Главное правило — методы должны быть осмысленными действиями именно для этой структуры. Если методов становится слишком много и они начинают «раздувать» код, это сигнал, что логику стоит разбить: вынести часть поведения в отдельные типы или интерфейсы.


