Зачем изменять сигнатуру метода при композиции в Go
Что проверяют
Понимаете ли вы, что встраивание в Go не наследование: методы продвигаются, но не переопределяются виртуально. Следом обычно спрашивают, какой метод вызовется, если встроенный тип обращается к нему сам, и почему после правки тип перестал реализовывать интерфейс.
Как отвечать
- продвижение: методы встроенного типа становятся методами внешнего, и внешний тип через них реализует те же интерфейсы;
- затенение по имени: метод внешнего типа с тем же именем скрывает продвинутый, причём сигнатура может быть любой. Перегрузки в Go нет, двух методов с одним именем у типа не бывает. Исходный остаётся доступен явно:
s.Logger.Log(...); - зачем менять сигнатуру: чтобы адаптировать встроенный тип под свой контракт. Обёртка над клиентом добавляет
ctx context.Context, возвращаетerror, прячет лишние параметры. Это адаптер; - когда сигнатуру сохраняют: декоратор с логированием, метриками или ретраями оставляет её прежней, добавляет поведение и зовёт встроенный метод. Тип по-прежнему подходит под интерфейс;
- цена изменения: с новой сигнатурой внешний тип перестаёт реализовывать интерфейс, который получал бесплатно. Закрепите проверку строкой
var _ io.Writer = (*MyWriter)(nil), тогда ошибка всплывёт при компиляции.
Сильный пример из стандартной библиотеки: структура со встроенным http.ResponseWriter видит только методы интерфейса и теряет, например, Flush исходного типа, поэтому проверка w.(http.Flusher) молча даёт false. Для этого в Go 1.20 появился http.ResponseController, который добирается до исходного writer через метод Unwrap.
Где спотыкаются
Называют это переопределением и ждут полиморфизма. Если метод встроенного типа вызывает Log, выполнится его собственный Log, а не версия внешнего типа, поэтому шаблонный метод из Java так не переносится.
Меняют сигнатуру и не замечают потери интерфейса. При явном присваивании это ошибка компиляции, а при проверке через утверждение типа код просто уйдёт в другую ветку.
Встраивают тип ради одного метода и выставляют наружу весь его API. Если сигнатуры всё равно меняются, честнее обычное именованное поле и явные методы.