Поиск ошибки: структура встраивает два типа, каждый из которых предоставляет метод, совпадающий с методом интерфейса. Считает ли Go такую структуру реализующей этот интерфейс?
Нет, если оба метода продвигаются на одинаковую глубину и создают неоднозначность. Такой метод не входит в методный набор внешней структуры как однозначно выбранный, поэтому структура не реализует интерфейс.
Встраивание типов в Go предназначено для композиции и автоматического продвижения методов без наследования. Это позволяет внешнему типу использовать поведение вложенного типа и тем самым реализовывать интерфейсы с меньшим количеством явного кода.
Однако Go не выбирает метод произвольно при конфликте имен. Неоднозначность запрещает неявное продвижение, чтобы поведение зависело от структуры типов, а не от неочевидного правила выбора.
Если структура встраивает два типа с одноимённым методом, обращение к этому методу через внешнюю структуру становится неоднозначным. Тот же конфликт влияет на методный набор и, следовательно, на проверку реализации интерфейса.
Ошибочно считать, что наличие двух подходящих методов достаточно. Для реализации интерфейса метод должен быть доступен у структуры однозначно; Go не объединяет и не выбирает конфликтующие методы автоматически.
При поиске метода Go учитывает уровень вложенности. Если метод найден у самой структуры, он имеет приоритет над продвигаемыми методами. Но если два встроенных типа на одном уровне предоставляют метод с одинаковым именем, возникает неоднозначность.
В такой ситуации метод не считается доступным через обычный селектор внешней структуры. Поэтому требование интерфейса не выполняется, даже если сигнатуры обоих конфликтующих методов совпадают с сигнатурой интерфейса.
Проблему можно устранить, явно объявив метод у внешней структуры. Такой метод становится методом самой структуры и однозначно удовлетворяет интерфейсу; внутри него можно явно выбрать один из встроенных методов. Другой вариант — обращаться к конкретному встроенному полю, но это не добавляет метод во внешний методный набор.
В примере оба Work продвигаются из A и B, поэтому до объявления собственного метода S интерфейс не реализуется. После явного объявления S.Work выбор становится однозначным.
Команда добавила в структуру сервиса два встроенных компонента: один отвечает за логирование, другой — за метрики. Оба компонента имеют метод Name, а внешний сервис должен удовлетворять интерфейсу, содержащему Name.
Первый вариант — оставить автоматическое продвижение. Его плюс — меньше кода, но компилятор отклоняет реализацию из-за неоднозначности. Второй вариант — переименовать методы компонентов; это устраняет конфликт, но может затронуть их публичные контракты.
Выбран явный метод Name у внешнего сервиса с делегированием к нужному компоненту. Такой подход сохраняет существующие типы, документирует намеренный источник значения и делает реализацию интерфейса стабильной. Минус — появляется небольшой слой явного кода, который нужно поддерживать.
Нет. Для интерфейсов одинаковые методы при встраивании могут объединяться, но для структуры, в которую встроены два конкретных типа, одинаковые продвигаемые методы всё равно создают неоднозначный селектор. Внешняя структура не получает однозначный метод автоматически.
Да. Метод, объявленный непосредственно у внешней структуры, находится на более близком уровне и скрывает одноимённые продвигаемые методы. Он становится единственным методом, используемым при проверке интерфейса и вызове через внешнюю структуру.
Нужно отдельно учитывать методные наборы значения и указателя на внешнюю структуру. Даже если один метод доступен только через указатель, конфликт и итоговая реализация зависят от того, какие методы реально продвигаются в конкретный методный набор. Проверку следует выполнять для точного типа (S или *S), а не предполагать, что реализация одинакова для обоих.