Проверьте, удовлетворяет ли Service интерфейсу Logger, и назовите причину ошибки компиляции.
package main
type Logger interface {
Log(...string)
}
type Service struct{}
func (Service) Log([]string) {}
var _ Logger = Service{}
func main() {}
Service не удовлетворяет интерфейсу Logger: методы Log(...string) и Log([]string) имеют разные сигнатуры. В Go вариативный параметр ...string не является эквивалентом параметра типа []string, поэтому объявление var _ Logger = Service{} завершится ошибкой компиляции.
Go использует структурную реализацию интерфейсов: тип не объявляет, что реализует интерфейс, а автоматически соответствует ему при совпадении метода и его сигнатуры. Такой подход уменьшает связанность между пакетами и позволяет определять интерфейсы на стороне потребителя.
Чтобы вызов через интерфейс был предсказуемым, проверяется не только имя метода, но и его точная сигнатура. Вариативность параметра является частью сигнатуры, поскольку она определяет правила вызова метода.
Метод Log(...string) можно вызвать с любым количеством отдельных аргументов, например Log("a", "b"). Метод Log([]string) принимает ровно один аргумент — срез строк, поэтому его вызов выглядит как Log([]string{"a", "b"}).
Если считать эти методы взаимозаменяемыми, вызов через интерфейс мог бы не соответствовать реальному методу конкретного типа. Поэтому компилятор отклоняет реализацию ещё до выполнения программы.
В объявлении интерфейса Log(...string) означает вариативный метод. В объявлении типа Log([]string) указан обычный метод с одним параметром типа []string; автоматического преобразования среза в набор вариативных аргументов при проверке реализации интерфейса нет.
Исправить код можно одним из двух способов:
Теперь сигнатуры совпадают, и Service реализует Logger. Внутри метода переменная messages имеет тип []string, но это не меняет того, что внешняя сигнатура метода является вариативной.
Обратный вариант тоже допустим: можно изменить интерфейс на Log([]string). Выбор зависит от контракта: вариативная форма удобнее для вызывающего кода, когда сообщения уже представлены отдельными значениями, а форма со срезом лучше подчёркивает, что метод получает готовую коллекцию.
Допустим, прикладной код должен передавать логгер в библиотеку. Вариант с Log(...string) позволяет писать logger.Log("подключение", "успешно"), но при передаче уже собранного среза потребуется распаковка: logger.Log(messages...). Вариант с Log([]string) принимает срез напрямую, однако каждый вызов с отдельными сообщениями требует явного создания среза.
Можно было бы объявить два метода с одинаковым именем для поддержки обоих вариантов, но Go не поддерживает перегрузку методов. Поэтому следует выбрать одну сигнатуру интерфейса по преобладающему сценарию использования; для общего логгера обычно удобна вариативная форма, а для пакетной обработки — форма со срезом.
Можно ли передать []string в Log(...string) без распаковки?
Нет. Срез можно передать как набор вариативных аргументов только с оператором ...: logger.Log(messages...). Вызов logger.Log(messages) передаст один аргумент типа []string и не соответствует параметру string.
Станет ли метод с Log(...any) реализацией интерфейса с Log(...string)?
Нет. any и string — разные типы параметров, а сигнатуры интерфейсных методов должны совпадать точно. Возможность передать строку в параметр any не делает методы взаимозаменяемыми для реализации интерфейса.
Можно ли адаптировать несовпадающий метод без изменения исходного типа?
Да, через отдельный тип-адаптер, реализующий нужный интерфейс и делегирующий вызов исходному объекту. Это сохраняет исходный API, но добавляет небольшой слой кода; прямое присваивание исходного типа интерфейсной переменной по-прежнему будет невозможно.