Композиция интерфейсов
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 31 из 107.
Go позволяет создавать более крупные интерфейсы путем композиции более мелких. Вместо того чтобы определять один большой интерфейс со множеством методов, вы встраиваете существующие интерфейсы внутрь определения нового интерфейса.
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriter объединяет оба интерфейса
type ReadWriter interface {
Reader
Writer
}Интерфейс ReadWriter теперь требует наличия обоих методов: Read() и Write(). Любой тип, реализующий оба метода, удовлетворяет интерфейсу ReadWriter, а также интерфейсам Reader и Writer по отдельности.
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // File удовлетворяет ReadWriter
}Этот подход на основе композиции позволяет сохранять интерфейсы небольшими и узкоспециализированными. Вы можете комбинировать их по мере необходимости, создавая точные контракты для ваших функций. Стандартная библиотека Go широко использует этот паттерн, например, io.ReadWriter, который объединяет io.Reader и io.Writer.
Задание
ЛегкоДавайте создадим систему медиаплеера, которая демонстрирует композицию интерфейсов. Вы создадите небольшие специализированные интерфейсы, а затем объедините их в более мощный составной интерфейс, которому может соответствовать один тип.
Вы организуете свой код в двух файлах:
media.go: Определите три интерфейса и структуру, которая реализует их все:- Интерфейс
Playerс методомPlay() string - Интерфейс
Recorderс методомRecord(content string) string - Интерфейс
MediaDevice, который объединяет в себеPlayerиRecorder - Структуру
SmartDeviceс полемName, которая реализует все необходимые методы
- Интерфейс
main.go: Создайте функции, демонстрирующие, как одно и то же устройствоSmartDeviceможет использоваться через различные типы интерфейсов. Напишите три функции:UsePlayer, которая принимаетPlayerи вызывает его методPlayUseRecorder, которая принимаетRecorderи строку контента, затем вызываетRecordUseMediaDevice, которая принимаетMediaDeviceи строку контента, затем вызывает иRecord, иPlay
SmartDeviceи продемонстрируйте его использование через все три типа интерфейсов.
Будут предоставлены следующие входные данные:
- Строка 1: Имя устройства
- Строка 2: Контент для записи
Ваши методы должны возвращать строки в следующих форматах:
Play():[Name] is playingRecord(content):[Name] recorded: [content]
Например, для MyPhone и voice memo ваш вывод должен быть следующим:
MyPhone is playing
MyPhone recorded: voice memo
MyPhone recorded: voice memo
MyPhone is playingПервые две строки показывают устройство, используемое через отдельные интерфейсы (Player и Recorder). Последние две строки показывают его использование через составной интерфейс MediaDevice, который требует наличия обеих возможностей. Обратите внимание, как ваш единственный SmartDevice удовлетворяет всем трем интерфейсам, так как он обладает обоими необходимыми методами.
Шпаргалка
Go позволяет создавать более крупные интерфейсы путем композиции более мелких, встраивая существующие интерфейсы в определение нового интерфейса:
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriter объединяет оба интерфейса
type ReadWriter interface {
Reader
Writer
}Любой тип, реализующий все методы встроенных интерфейсов, удовлетворяет составному интерфейсу:
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // File удовлетворяет ReadWriter
}Тип, реализующий составной интерфейс, также удовлетворяет каждому из отдельных встроенных интерфейсов. Такой подход к композиции позволяет сохранять интерфейсы небольшими и узкоспециализированными, в то же время позволяя создавать точные контракты для ваших функций.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Реализовать UsePlayer, которая принимает Player и вызывает его метод Play
// Она должна выводить результат Play()
// TODO: Реализовать UseRecorder, которая принимает Recorder и строку content
// Она должна выводить результат Record(content)
// TODO: Реализовать UseMediaDevice, которая принимает MediaDevice и строку content
// Она должна выводить результат Record(content), а затем Play()
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
deviceName := scanner.Text()
scanner.Scan()
content := scanner.Text()
// TODO: Создать SmartDevice с заданным именем
// TODO: Использовать устройство через интерфейс Player
// TODO: Использовать устройство через интерфейс Recorder
// TODO: Использовать устройство через интерфейс MediaDevice
_ = deviceName
_ = content
fmt.Println("Implement the solution")
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП в Go
Внешние файлыРабочее пространство и модули GoПакеты и импортыЭкспортируемые и неэкспортируемые именаВведение в ООП в GoСтруктуры как классыОпределение методов структурПолучатели-указатели и получатели-значенияИнициализация структурФункции-конструкторыИтоги — Простой калькулятор4Интерфейсы
Введение в интерфейсыНеявная реализацияИнтерфейс как контрактПустой интерфейс (any)Утверждение типаПереключатель типовКомпозиция интерфейсовИнтерфейсы Stringer и ErrorПовторение: Калькулятор фигур7Инкапсуляция
Экспортируемые и неэкспортируемые поляИнкапсуляция на уровне пакетовГеттеры и сеттерыСокрытие информации в GoИтоги — Записи о студентах10Обобщения (Generics) (Go 1.18+)
Введение в GenericsПараметры типовОграничения типовОбобщенные структурыОбходной путь для обобщенных методовИтоги — Обобщенная коллекция13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн Abstract FactoryПаттерн ObserverПаттерн Strategy2Глубокое погружение в типы и структуры
Базовые и составные типыОпределение пользовательских типовТеги структурАнонимные структурыВложенные структурыНулевые значения и значения по умолчаниюПовторение — Контактная книга5Композиция вместо наследования
Почему в Go нет наследованияОсновы встраивания структурПродвижение методовВстраивание нескольких структурВстраивание против агрегацииЗатенение встроенных методовИтоги — Иерархия сотрудников8Обработка ошибок и ООП
Интерфейс errorПользовательские типы ошибокОбертывание ошибок (fmt.Errorf)Sentinel-ошибкиerrors.Is() и errors.As()Panic, Defer и RecoverИтоги — Парсер файлов11Стандартная библиотека и ООП
io.Reader и io.Writersort.InterfaceИнтерфейс fmt.Stringerencoding/json со структурамиИнтерфейс http.HandlerПовторение: модели REST API14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикMiddleware как Декоратор3Указатели и память
Основы указателей в GoУказатели на структурыПередача по значению и по ссылкеФункция new()Сборка мусора в GoПовторение: Конструктор связного списка6Полиморфизм в Go
Полиморфизм через интерфейсыУтиная типизация в GoПравила реализации интерфейсовПолиморфные коллекцииВнедрение зависимостейИтоги — Обработчик платежей9Конкурентность и ООП
Основы горутинКаналы и взаимодействиеБуферизованные и небуферизованные каналыОператор selectsync.Mutex и sync.RWMutexsync.WaitGroupПроектирование потокобезопасных структурПовторение — Worker Pool