Ответ
В Go используется неявная (или структурная) типизация для интерфейсов. Это означает, что тип удовлетворяет интерфейсу, если он реализует все методы, объявленные в этом интерфейсе. Не требуется никакого ключевого слова типа implements
.
Этот подход известен как "утиная типизация": "Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, и есть утка".
// Объявляем интерфейс
type Speaker interface {
Speak() string
}
// Тип Dog неявно реализует Speaker, так как у него есть метод Speak()
type Dog struct{}
func (d Dog) Speak() string {
return "Гав!"
}
// Тип Cat также его реализует
type Cat struct{}
func (c *Cat) Speak() string {
return "Мяу!"
}
Ключевые аспекты механизма интерфейсов:
-
Неявная реализация: Главное преимущество — слабая связанность (low coupling). Пакет, определяющий интерфейс, не должен знать о типах, которые его реализуют, и наоборот.
-
Статическая проверка: Соответствие интерфейсу проверяется на этапе компиляции. Если вы попытаетесь использовать тип там, где ожидается интерфейс, но тип не реализует все методы, компилятор выдаст ошибку.
-
Проверка во время компиляции: Можно явно проверить, что тип реализует интерфейс, с помощью специальной конструкции. Это помогает отловить ошибки на раннем этапе.
// Эта строка вызовет ошибку компиляции, если *Cat перестал // удовлетворять интерфейсу Speaker. var _ Speaker = (*Cat)(nil)
-
Разница между ресиверами (value vs pointer): Это важный нюанс.
- Если метод имеет value receiver (
func (d Dog) ...
), то и значение (Dog
), и указатель (*Dog
) удовлетворяют интерфейсу. - Если метод имеет pointer receiver (
func (c *Cat) ...
), то только указатель (*Cat
) удовлетворяет интерфейсу.
- Если метод имеет value receiver (
-
Пустой интерфейс (
interface{}
): Это интерфейс без методов. Поскольку у него нет требований, любой тип ему удовлетворяет. Он используется для работы со значениями неизвестного типа.