При вызове метода через интерфейсное значение какой механизм определяет реализацию, которая будет выполнена?
Интерфейсный вызов выполняется по динамическому типу значения, хранящегося внутри интерфейса. Компилятор проверяет совместимость типа с интерфейсом через его набор методов, а во время выполнения выбирается реализация метода этого конкретного динамического типа.
Обе структуры не объявляют явную связь с Speaker: соответствие определяется структурно. При первом вызове выбирается Dog.Speak, при втором — Cat.Speak.
Интерфейсы Go решают задачу отделения кода, который использует поведение, от конкретной реализации. В отличие от наследования, типу не требуется заранее объявлять, что он реализует интерфейс.
Такой подход упрощает замену реализаций и снижает связанность пакетов. Код может зависеть от небольшого набора операций, не зная внутреннего устройства конкретного типа.
Функция, принимающая интерфейс, заранее не знает, значение какого конкретного типа ей передадут. Если ориентироваться только на статический тип параметра, невозможно корректно определить, код какого метода нужно выполнить.
Ошибка в понимании механизма приводит к неверным ожиданиям: например, разработчик может считать, что вызывается метод интерфейса с общей реализацией или что выбор метода происходит по типу переменной, а не по фактическому значению.
Интерфейсный тип задаёт набор методов. Конкретный тип удовлетворяет интерфейсу, если его набор методов содержит все требуемые методы с совпадающими именами, параметрами и результатами.
Интерфейсное значение концептуально содержит динамический тип и динамическое значение. Статический тип переменной остаётся интерфейсным, но при вызове метода среда выполнения использует динамический тип, чтобы направить вызов к соответствующей реализации.
Получатель метода важен для соответствия интерфейсу. Метод с получателем-значением входит в набор методов и значения, и указателя на этот тип; метод с получателем-указателем входит только в набор методов указателя. Поэтому значение структуры может не удовлетворять интерфейсу, которому удовлетворяет указатель на эту структуру.
Динамическая диспетчеризация не означает, что интерфейс поддерживает произвольные методы конкретного типа. Через интерфейс доступны только методы, объявленные в типе интерфейса; для доступа к другим операциям требуется проверка или преобразование типа, что увеличивает связанность с конкретной реализацией.
Интерфейсный вызов обычно удобен, когда набор поведения стабилен, а реализации взаимозаменяемы. Но интерфейс не следует вводить автоматически: слишком широкий интерфейс усложняет тестирование, реализацию и понимание зависимостей.
Сервис отправляет уведомления через разные каналы: электронную почту и очередь сообщений. Основной код должен вызывать единое действие отправки и не зависеть от деталей конкретного транспорта.
Можно использовать проверку конкретного типа и switch. Это позволяет обращаться к уникальным возможностям каждого транспорта, но быстро увеличивает связанность: при добавлении канала придётся изменять центральную функцию.
Другой вариант — передавать интерфейс с одной операцией отправки. Реализации явно разделяют общий контракт, тестовый отправитель подставляется без изменения сервиса, а выбор метода выполняется по динамическому типу переданного значения.
Выбран интерфейс с минимальным методом отправки. Это решение оправдано, потому что сервису действительно нужно только общее поведение; специфичные операции транспортов остаются за пределами его ответственности. В результате добавление нового канала не требует менять код сервиса.
Оба типа будут допустимыми значениями этого интерфейса. При вызове метода реализация выбирается отдельно для каждого интерфейсного значения по его динамическому типу; наличие одинаковых сигнатур не объединяет типы и не создаёт между ними наследование.
Причина в наборе методов. Методы с указательным получателем доступны через указатель, поскольку реализация может изменять объект; такой метод не входит в набор методов самого значения. Поэтому передача значения туда, где требуется интерфейс с этим методом, завершится ошибкой компиляции, если у значения нет полного набора методов.
Нельзя выполнить такой вызов напрямую: статический тип выражения — интерфейс, поэтому компилятор разрешает только методы этого интерфейса. Доступ к дополнительному поведению возможен после проверки динамического типа, но это связывает код с конкретным типом и ослабляет преимущество абстракции.