0Pricing
Go Academy · Урок

Определение и реализация интерфейсов

Неявная реализация и пустой интерфейс

«Определение и реализация интерфейсов» — бесплатный урок Go Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Go Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Go Academy содержит 4 уроков всего.

Что такое интерфейс

Интерфейс в Go — это набор сигнатур методов. Любой тип, реализующий все эти методы, неявно соответствует интерфейсу — ключевое слово implements не требуется. Это называется структурной типизацией или утиной типизацией.

Объявление интерфейса

Определите интерфейс с помощью ключевого слова type:

package main
import "fmt"

type Shape interface {
    Area() float64
    Perimeter() float64
}

type Circle struct{ Radius float64 }
func (c Circle) Area() float64      { return 3.14159 * c.Radius * c.Radius }
func (c Circle) Perimeter() float64 { return 2 * 3.14159 * c.Radius }

func main() {
    var s Shape = Circle{5}
    fmt.Printf("Area: %.2f\n", s.Area())
}

Неявное соответствие

Объявлять о намерении не требуется. Если у типа есть подходящие методы, он автоматически соответствует интерфейсу:

package main
import "fmt"

type Greeter interface{ Greet() string }

type English struct{}
func (e English) Greet() string { return "Hello" }

type Spanish struct{}
func (s Spanish) Greet() string { return "Hola" }

func say(g Greeter) { fmt.Println(g.Greet()) }

func main() {
    say(English{})
    say(Spanish{})
}

Пустой интерфейс

interface{} (или any в Go 1.18 и более поздних версиях) поддерживается каждым типом. Он используется, когда тип неизвестен во время компиляции:

package main
import "fmt"

func printAny(v any) { fmt.Printf("%T: %v\n", v, v) }

func main() {
    printAny(42)
    printAny("hello")
    printAny([]int{1,2,3})
}

Значения интерфейсов

Значение интерфейса содержит две вещи: конкретный тип и конкретное значение. У нулевого интерфейса оба компонента имеют нулевое значение:

package main
import "fmt"

type Animal interface{ Sound() string }
type Dog struct{}
func (d Dog) Sound() string { return "woof" }

func main() {
    var a Animal
    fmt.Println(a == nil) // true
    a = Dog{}
    fmt.Println(a == nil) // false
    fmt.Println(a.Sound())
}

Соответствие нескольким интерфейсам

Тип может одновременно соответствовать нескольким интерфейсам:

package main
import "fmt"

type Reader interface{ Read() string }
type Writer interface{ Write(string) }
type ReadWriter interface{ Reader; Writer }

type File struct{ content string }
func (f *File) Read() string       { return f.content }
func (f *File) Write(s string)     { f.content = s }

func main() {
    var rw ReadWriter = &File{}
    rw.Write("hello")
    fmt.Println(rw.Read())
}

Присваивание интерфейсов

Более общий интерфейс может содержать более узкое конкретное значение. Для присваивания между типами интерфейсов конкретный тип должен соответствовать обоим интерфейсам:

package main
import "fmt"

type Stringer interface{ String() string }

type Point struct{ X, Y int }
func (p Point) String() string { return fmt.Sprintf("(%d,%d)", p.X, p.Y) }

func main() {
    var s Stringer = Point{3, 4}
    fmt.Println(s.String())
}

Ловушка нулевого интерфейса

Интерфейс, не равный нулю и содержащий нулевое конкретное значение, не равен нулю (NOT):

package main
import "fmt"

type MyErr struct{}
func (e *MyErr) Error() string { return "oops" }

func bad() error {
    var e *MyErr // nil pointer
    return e     // returns non-nil interface!
}

func main() {
    err := bad()
    fmt.Println(err == nil) // false — common gotcha
}

Интерфейсы упрощают тестирование

Определение зависимостей как интерфейсов позволяет заменять настоящие реализации заглушками в тестах:

package main
import "fmt"

type EmailSender interface{ Send(to, body string) error }

type MockSender struct{ Sent []string }
func (m *MockSender) Send(to, body string) error {
    m.Sent = append(m.Sent, to)
    return nil
}

func notify(s EmailSender, addr string) { s.Send(addr, "hello") }

func main() {
    mock := &MockSender{}
    notify(mock, "a@b.com")
    fmt.Println(mock.Sent) // [a@b.com]
}

Принимайте интерфейсы, возвращайте структуры

Рекомендуемый подход в Go: параметры функций должны быть интерфейсами (для гибкости), а возвращаемые типы — конкретными типами (чтобы вызывающим сторонам было ясно, что именно они получают):

package main

type Logger interface{ Log(string) }

type FileLogger struct{}
func (f *FileLogger) Log(s string) {}

func NewFileLogger() *FileLogger { // returns concrete
    return &FileLogger{}
}

func process(l Logger) {} // accepts interface

func main() { process(NewFileLogger()) }

Делайте интерфейсы небольшими

Стандартная библиотека Go отдаёт предпочтение крошечным интерфейсам: у io.Reader один метод, у io.Writer один метод. Небольшие интерфейсы проще комбинировать и тестировать с заглушками.

Быстрая проверка

Нужно ли типу объявлять, что он реализует интерфейс?

Итоги: интерфейсы

Главные выводы:

  • Интерфейсы определяют поведение, а не данные
  • Соответствие является неявным — ключевое слово implements не требуется
  • Пустой интерфейс (any) принимает любой тип
  • Помните о ловушке нулевого интерфейса
  • Делайте интерфейсы небольшими и специализированными

Практическое задание

Определите интерфейс Storage с методами Save(key, value string) error и Load(key string) (string, error). Реализуйте его с помощью MemStorage на основе отображения и протестируйте его.

Часто задаваемые вопросы

Урок «Определение и реализация интерфейсов» бесплатный?

Да — полный текст урока «Определение и реализация интерфейсов» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Go Academy, подпишись на CoddyKit PRO. Курс Go Academy содержит 4 уроков всего.

Чему я научусь в уроке «Определение и реализация интерфейсов»?

Неявная реализация и пустой интерфейс Ты практикуешь Go Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Go Academy?

Предыдущий опыт не требуется. Go Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Определение и реализация интерфейсов»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Go Academy?

Да. Каждый урок Go Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Определение и реализация интерфейсов
  2. Интерфейсы основной библиотеки
  3. Утверждения типов и переключатели типов
  4. Композиция интерфейсов и лучшие практики
← Назад к Go Academy