Полиморфные коллекции
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 44 из 107.
Одно из самых мощных применений интерфейсов — хранение разных типов в одной коллекции. Срез типа интерфейса может содержать любое значение, удовлетворяющее этому интерфейсу, позволяя объединять связанные, но разные типы.
Рассмотрим ситуацию, когда вам нужно управлять различными фигурами. Вместо отдельных срезов для каждого типа можно использовать один срез типа интерфейса:
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
Срез shapes содержит и круги, и прямоугольники. При переборе каждый элемент отвечает на вызов Area() в соответствии со своим фактическим типом. Это применение полиморфизма к коллекциям: один и тот же цикл единообразно обрабатывает все типы фигур.
Полиморфные коллекции необходимы при создании систем, обрабатывающих разные элементы: системы уведомлений, отправляющей электронные письма и SMS-сообщения, игры, обновляющей разные типы врагов, или обработчика документов, работающего с несколькими форматами файлов. Коллекции не важны конкретные типы: важно лишь, чтобы каждый элемент предоставлял необходимое поведение.
Задание
ЛегкоДавайте создадим систему управления задачами, которая обрабатывает разные типы задач через единый интерфейс. Вы создадите различные типы задач и будете обрабатывать их вместе в полиморфной коллекции.
Вы распределите код по трём файлам:
task.go: Определите интерфейсTask, требующий методSummary() string. Этот интерфейс станет общим контрактом, которому должны соответствовать все типы задач.types.go: Создайте три разных типа задач, каждый из которых реализует интерфейсTask:BugFixс полямиID(string) иSeverity(string). МетодSummary()возвращаетBug #[ID] ([Severity])Featureс полямиName(string) иPoints(int). МетодSummary()возвращаетFeature: [Name] - [Points] ptsDocumentationс полемTopic(string). МетодSummary()возвращаетDocs: [Topic]
main.go: Создайте функциюPrintBacklog, которая принимает срезTaskи выводит сводку по каждой задаче на отдельной строке. Считайте сведения о задачах из входных данных, создайте по одной задаче каждого типа, соберите их в один срез[]Taskи передайте его вPrintBacklog.
Будут предоставлены следующие входные данные:
- Строка 1: ID ошибки
- Строка 2: критичность ошибки
- Строка 3: название функциональности
- Строка 4: количество баллов функциональности (целое число)
- Строка 5: тема документации
Например, если заданы 1042, critical, Themes, 8 и API, результат должен быть таким:
Bug #1042 (critical)
Feature: Themes - 8 pts
Docs: APIПреимущество такого подхода в том, что PrintBacklog не нужна отдельная логика для ошибок, функциональностей или документации. Функция просто перебирает срез и вызывает Summary() для каждого элемента. Каждый тип задачи возвращает собственную уникальную строку в заданном формате, что показывает, как полиморфные коллекции позволяют единообразно обрабатывать разные типы.
Попробуйте сами
package main
import (
"fmt"
)
// TODO: Создайте функцию PrintBacklog, которая принимает срез Task
// и выводит краткое описание каждой задачи на отдельной строке
func main() {
// Считайте входные данные
var bugID string
var bugSeverity string
var featureName string
var featurePoints int
var docTopic string
fmt.Scanln(&bugID)
fmt.Scanln(&bugSeverity)
fmt.Scanln(&featureName)
fmt.Scanln(&featurePoints)
fmt.Scanln(&docTopic)
// TODO: Создайте по одной задаче каждого типа (BugFix, Feature, Documentation)
// TODO: Соберите все задачи в один срез []Task
// TODO: Вызовите PrintBacklog, передав срез задач
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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Потренируйтесь самостоятельно: Онлайн-компилятор Go