История вопроса:
Go реализовал простую, но мощную концепцию методов и интерфейсов, основанную на duck typing. Методы можно вешать только на именованные типы (struct или alias), а интерфейсы являются набором методов. Это ключ к полиморфизму и абстракциям без необходимости явного указания implements.
Проблема:
Программисты, пришедшие из Java или C#, часто ожидают явных ключевых слов implements или extends и путаются в различиях между методами с ресивером по значению и по указателю, что приводит к непредсказуемому поведению и ошибкам при реализации интерфейсов.
Решение:
Определение метода и интерфейса:
type User struct { Name string } func (u User) Greet() string { return "Hello, " + u.Name } func (u *User) SetName(name string) { u.Name = name } type Greeter interface { Greet() string }
Ключевые особенности:
Можно ли объявить методы для типа-алиаса с базовым типом int или string?
Да, если это именованный тип, например:
type MyInt int func (m MyInt) Double() int { return int(m) * 2 }
Чем отличается реализация интерфейса через методы с указателем и значением?
Если метод интерфейса объявлен как (T), то оба T и *T реализуют интерфейс. Если ( *T), только *T удовлетворяет интерфейсу. Это критично для передачи структуры в функции, принимающие интерфейс.
Как работает "zero value" для интерфейса и что будет, если вызвать метод на nil-значении?
Интерфейсная переменная без инициализации равна nil, попытка вызвать метод на nil-полем паникнет, если не реализована явная обработка nil-pointer.
Тип объявлен с методами по указателю (*T), интерфейс ожидает методы по значению (T), и структура не компилируется при попытке использовать в интерфейсах. Попытка объявить метод для переменной типа []User, а не для типа User.
Плюсы:
Минусы:
Все методы, реализующие интерфейсы, объявлены с корректным ресивером. Не используются интерфейсы там, где это не нужно (concrete type где можно, polimorphism где нужно).
Плюсы:
Минусы: