Композиция интерфейсов и лучшие практики
Небольшие интерфейсы и инверсия зависимостей
«Композиция интерфейсов и лучшие практики» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Определение и реализация интерфейсов
- Интерфейсы основной библиотеки
- Утверждения типов и переключатели типов
- Композиция интерфейсов и лучшие практики