0Pricing
Go Academy · Урок

Композиция интерфейсов и лучшие практики

Небольшие интерфейсы и инверсия зависимостей

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

Композиция интерфейсов

Интерфейсы Go могут встраивать другие интерфейсы, чтобы составлять более крупные интерфейсы:

package main
import "io"

// io.ReadWriter composes Reader and Writer
type ReadWriter interface {
    io.Reader
    io.Writer
}

// Your own composition:
type Processor interface {
    io.Reader
    Process() error
    io.Closer
}

func main() { _ = (*Processor)(nil) }

Принцип небольших интерфейсов

Наиболее мощные интерфейсы Go имеют один или два метода. Это позволяет максимально увеличить количество типов, которые могут им соответствовать:

  • io.Reader — 1 метод
  • io.Writer — 1 метод
  • error — 1 метод
  • fmt.Stringer — 1 метод

Крупные интерфейсы сложнее имитировать и тестировать.

Инверсия зависимостей

Принимайте интерфейсы в параметрах функций, а не конкретные типы. Это уменьшает связанность кода с конкретными реализациями:

package main
import "fmt"

type Store interface {
    Get(key string) (string, bool)
    Set(key, val string)
}

func cache(s Store, key, value string) {
    if _, ok := s.Get(key); !ok {
        s.Set(key, value)
    }
}

type MapStore struct{ m map[string]string }
func (ms *MapStore) Get(k string) (string, bool) { v, ok := ms.m[k]; return v, ok }
func (ms *MapStore) Set(k, v string) { ms.m[k] = v }

func main() {
    s := &MapStore{m: map[string]string{}}
    cache(s, "x", "hello")
    v, _ := s.Get("x")
    fmt.Println(v)
}

Разделение интерфейсов

Разделяйте крупные интерфейсы на небольшие специализированные интерфейсы. Клиенты зависят только от используемых ими методов:

package main

// Bad: large interface forces all methods
type BigStorage interface {
    Read(key string) string
    Write(key, val string)
    Delete(key string)
    List() []string
    Backup() error
}

// Good: small, focused interfaces
type Reader interface{ Read(key string) string }
type Writer interface{ Write(key, val string) }
type Lister interface{ List() []string }

func readAll(r Reader) {} // only needs Read

func main() { _ = readAll }

Возврат конкретных типов

Конструкторы должны возвращать конкретные типы, а не интерфейсы. Вызывающий код всегда может присвоить конкретный тип переменной-интерфейсу:

package main

type Logger interface{ Log(string) }
type ConsoleLogger struct{}
func (c *ConsoleLogger) Log(s string) {}

// Good: return concrete *ConsoleLogger
func NewConsoleLogger() *ConsoleLogger { return &ConsoleLogger{} }

// Avoid: return Logger (hides the concrete type)
// func NewConsoleLogger() Logger { return &ConsoleLogger{} }

func main() { _ = NewConsoleLogger() }

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

Не создавайте интерфейс для каждого типа «на всякий случай». Создавайте интерфейсы только в следующих случаях:

  • У Вас уже есть или скоро появятся несколько реализаций
  • Вам необходимо имитировать зависимость в тестах
  • Вы создаёте библиотеку и хотите отделить вызывающий код от реализации

Встраивание интерфейсов для расширения

Встраивайте интерфейс, чтобы добавить необязательные методы со значениями по умолчанию:

package main
import "fmt"

type Handler interface{ Handle(msg string) }
type NamedHandler interface {
    Handler
    Name() string
}

func dispatch(h Handler) {
    if nh, ok := h.(NamedHandler); ok {
        fmt.Printf("[%s] ", nh.Name())
    }
    h.Handle("event")
}

type MyHandler struct{}
func (m MyHandler) Handle(msg string)  { fmt.Println(msg) }
func (m MyHandler) Name() string       { return "my" }

func main() { dispatch(MyHandler{}) }

Создание имитаций с помощью интерфейсов

Определяйте зависимости как интерфейсы; внедряйте имитации в тестах:

package main
import "fmt"

type Clock interface{ Now() int64 }

type MockClock struct{ T int64 }
func (m MockClock) Now() int64 { return m.T }

func isExpired(c Clock, expiresAt int64) bool {
    return c.Now() > expiresAt
}

func main() {
    mc := MockClock{T: 1000}
    fmt.Println(isExpired(mc, 500))  // true
    fmt.Println(isExpired(mc, 1500)) // false
}

Встраивание интерфейсов и структур для повторного использования

Используйте встраивание интерфейсов для композиции API, а встраивание структур — для повторного использования кода. Они служат разным целям, и их не следует путать.

Проверка соответствия интерфейсу во время компиляции

Используйте проверку во время компиляции с пустым идентификатором, чтобы убедиться, что Ваш тип соответствует интерфейсу:

package main

type Doer interface{ Do() }
type MyType struct{}
func (m *MyType) Do() {}

// Compile-time check — no runtime cost
var _ Doer = (*MyType)(nil)

func main() {}

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

Когда следует определять новый интерфейс в Go?

Повторение: рекомендации по работе с интерфейсами

Основные выводы:

  • Делайте интерфейсы небольшими — один или два метода
  • Составляйте интерфейсы из небольших интерфейсов
  • Принимайте интерфейсы, возвращайте конкретные типы
  • Создавайте интерфейсы для удобства тестирования и поддержки нескольких реализаций
  • Используйте проверку присваиванием пустому идентификатору во время компиляции

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

Спроектируйте интерфейс Notifier с методом Notify(msg string) error. Реализуйте его для EmailNotifier и SMSNotifier. Напишите функцию broadcast, которая отправляет сообщения в []Notifier.

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

Урок «Композиция интерфейсов и лучшие практики» бесплатный?

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

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

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

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

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

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

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

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

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

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

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