21 вопросов
В классическом виде (классы, наследование) - нет. Есть инкапсуляция (экспорт через регистр), полиморфизм через интерфейсы, методы у типов. Композиция через встраивание структур вместо наследования. ООП в Go - без иерархии классов.
Регистр имени: имена с заглавной буквы экспортируются (публичные), с маленькой - только внутри пакета. Поля структуры и методы подчиняются этому правилу. "Приватные" поля и методы доступны только в том же пакете. Сеттеры/геттеры - по соглашению, когда нужна логика доступа.
Через интерфейсы. Тип не объявляет интерфейс, но если у него есть нужные методы - он подходит. Функция принимает интерфейс и работает с любым типом, его реализующим. Подмена реализации (в том числе в тестах) - через разные типы, удовлетворяющие одному интерфейсу.
Ноль байт (или минимальный размер, чтобы адрес был уникальным - зависит от реализации). Несколько переменных типа struct{} могут иметь один и тот же адрес. Используется как маркер или в map/set для множеств без значения.
Как тип значения в map для set: map[T]struct{}. Сигнал в каналах: chan struct{}, done <- struct{}{}. Вместо bool когда важно только наличие. Как тип данных не хранит ничего - экономия памяти.
Метод с получателем-указателем: func (p *T) M(). Вызывается и для T, и для *T (компилятор подставит адрес). В методе можно менять поля получателя. Для больших структур и когда нужно изменять состояние используют pointer receiver.
Value: маленькие структуры, неизменяемые типы, когда не нужна мутация. Pointer: большие структуры (избежание копирования), когда метод меняет получателя или структура содержит мьютекс/слайс/map. Единообразие: если один метод с pointer receiver - обычно все с pointer.
Метод привязан к типу: func (r T) M(). Вызов: x.M(), в методе r - получатель. Функция не привязана: func F(x T). Методы могут быть только у типов, определённых в том же пакете. Интерфейсы требуют методы, не функции.
Включение типа без имени поля: type B struct { A }. Поля и методы A доступны на B напрямую (promoted): b.FieldA. Внешняя структура "включает" встроенную. Конфликт имён - явно указывать вложенный тип. Наследования нет - только композиция.
Встраивание - композиция: тип B содержит A, но B не "подтип" A. Нет виртуальных методов и переопределения в смысле ООП. Методы A продвигаются на B; при том же имени метод внешней структуры "затеняет". Нет полиморфизма по иерархии - полиморфизм через интерфейсы.
Зависимости определяются через интерфейсы, а не конкретные типы. Пакет верхнего уровня объявляет интерфейс с нужными методами; нижний уровень реализует его. Внедрение зависимостей - передача реализации (структуры) в конструктор или через опции. Тесты подставляют моки того же интерфейса.
Имена с заглавной буквы - экспортируемые (видны из других пакетов). С маленькой - неэкспортируемые (только текущий пакет). Применяется к типам, полям структур, функциям, методам, константам, переменным. Определяет публичный API пакета.
Нет. Методы можно объявлять только для типов, определённых в том же пакете. Для встроенных (int, string и т.д.) нужно объявить свой тип: type MyInt int, затем func (m MyInt) Foo() {}. MyInt и int - разные типы, приведение через MyInt(x).
Да. Поля выравниваются по границам (alignment); между полями может быть padding. Порядок полей от большего к меньшему по выравниванию часто уменьшает размер структуры. Пустые поля struct{} в конце могут не занимать места; в середине - могут для выравнивания.
Компилятор выравнивает поля (например, int64 по 8 байт). Неоптимальный порядок добавляет padding. Пример: два bool и int64 - лучше int64, затем bool, чтобы не было лишних байт между ними. unsafe.Sizeof и unsafe.Offsetof показывают размер и смещения.
Если встроенная и внешняя структура имеют метод с одним именем, вызывается метод внешней. Внешний метод "затеняет" встроенный. Доступ к встроенному явно: b.A.M(). Для переопределения поведения внешняя структура объявляет свой метод с тем же именем.
Объявить свой тип на основе стандартного: type MySlice []int. Для MySlice можно объявить методы. Нельзя добавить методы напрямую к []int. Приведение: MySlice(s), []int(m). Встроенные методы (len, cap и т.д.) остаются; свои добавляются отдельно.
Методы встроенного типа "поднимаются" на внешнюю структуру и вызываются как методы внешней. type B struct { A }; func (a A) F() {}; тогда b.F() валиден и вызывает A.F с получателем b.A. Если у B есть свой F, вызывается B.F (затенение).
Да, если все поля сравниваемы (comparable). Структуры с полями-слайсами, map или функциями сравнивать нельзя. == сравнивает поля поэлементно. Для сложных структур иногда удобнее сравнивать по отдельным полям или через reflect.DeepEqual (учитывает типы и вложенность).
Структура без имени типа: struct{ X int }{X: 1} или var v struct{ Name string }. Используется для разовых данных, JSON-ответов, тестовых фикстур. Нельзя объявить методы у анонимной структуры - только у именованного типа.
Поверхностное: копируются поля; если поле - указатель или слайс, копируется только указатель/заголовок, данные общие. Глубокое: рекурсивно копировать всё, включая данные по указателям и элементы слайсов. В Go копирование структуры по умолчанию поверхностное. Глубокое - вручную или через библиотеки/рефлексию.