Обобщенные структуры
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy — урок 70 из 107.
Как и функции, структуры также могут иметь параметры типа. Обобщенная структура позволяет создавать структуры данных, которые работают с любым типом, сохраняя при этом типобезопасность. Это идеально подходит для создания переиспользуемых контейнеров, таких как стеки, очереди или пары «ключ-значение».
Чтобы определить универсальную структуру, укажите параметр типа после имени структуры:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // hello
}В отличие от обобщенных функций, при создании экземпляра обобщенной структуры вы должны явно указывать тип. Go не может вывести тип только из контекста.
Обобщенные структуры могут иметь несколько параметров типа, что делает их идеальными для таких структур, как пары или словари (maps):
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25Вы также можете применять ограничения, чтобы гарантировать, что хранимые типы поддерживают определенные операции:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Обобщенные структуры составляют основу для создания типобезопасных, переиспользуемых структур данных в Go. В следующем уроке вы узнаете, как определять методы для этих обобщенных типов.
Задание
ЛегкоДавайте создадим универсальную систему инвентаризации, которая может отслеживать различные типы предметов! Вы создадите переиспользуемые структуры контейнеров, которые работают с любым типом, сохраняя при этом полную типизацию.
Вы организуете свой код в двух файлах:
inventory.go: Определите свои универсальные структуры контейнеров.Создайте универсальную структуру
Item[T any]с двумя полями:Name(string) иData(типа T). Это представляет собой любой предмет со связанными данными гибкого типа.Создайте универсальную структуру
Container[T any]с единственным полемItems, которое содержит срезItem[T]. Этот контейнер может хранить несколько предметов одного и того же типа данных.Создайте универсальную структуру с ограничением
PricedItem[T int | float64]с тремя полями:Name(string),Quantity(int) иPrice(типа T). Ограничение гарантирует, что цены всегда будут числовыми.Реализуйте функцию
NewContainer[T any]() *Container[T], которая создает и возвращает указатель на пустой Container.Реализуйте метод
Addдля*Container[T], который принимает имя (string) и данные (T), создает Item и добавляет его в срез Items контейнера.Реализуйте метод
CountдляContainer[T], который возвращает количество предметов в контейнере.main.go: Продемонстрируйте свои универсальные структуры с различными типами.Прочитайте тип предмета (
"string","int"или"priced"), затем прочитайте количество, а за ним — детали предметов.Для типа
"string": Прочитайте пары имени и строковых данных. СоздайтеContainer[string], добавьте все предметы, затем выведите каждый предмет в формате[Name]: [Data], а после — общее количество.Для типа
"int": Прочитайте пары имени и целочисленных данных. СоздайтеContainer[int], добавьте все предметы, затем выведите каждый предмет в формате[Name]: [Data], а после — общее количество.Для типа
"priced": Прочитайте тройки имени, количества и цены (как float). Создайте экземплярыPricedItem[float64]напрямую и выведите каждый в формате[Name] x[Quantity] @ [Price], где цена отображается с одним десятичным знаком.Выведите строку с количеством в формате:
"Total items: [count]"
Будут предоставлены следующие входные данные:
- Строка 1: Тип предмета (
"string","int"или"priced") - Строка 2: Количество предметов (целое число)
- Следующие строки: Детали предметов в зависимости от типа
Например, при вводе:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined PaperВаш вывод должен быть:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3И при вводе:
int
2
Apples
50
Oranges
30Ваш вывод должен быть:
Apples: 50
Oranges: 30
Total items: 2И при вводе:
priced
2
Widget
10
19.99
Gadget
5
49.50Ваш вывод должен быть:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2Обратите внимание, как одна и та же структура Container беспрепятственно работает как со строками, так и с целыми числами, в то время как PricedItem использует ограничение, чтобы гарантировать использование только числовых типов для цен. Вы должны явно указывать параметр типа при создании экземпляров этих универсальных структур.
Шпаргалка
Обобщенная структура позволяет создавать структуры данных, которые работают с любым типом, сохраняя при этом типобезопасность.
Чтобы определить обобщенную структуру, укажите параметр типа после имени структуры:
type Box[T any] struct {
Value T
}При создании экземпляра обобщенной структуры необходимо явно указать тип (Go не может вывести его автоматически):
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}Обобщенные структуры могут иметь несколько параметров типа:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}Вы можете применять ограничения, чтобы гарантировать, что хранимые типы поддерживают определенные операции:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Обобщенные структуры могут содержать поля, использующие параметр типа, включая срезы:
type Container[T any] struct {
Items []T
}Вы можете определять методы для обобщенных структур и создавать обобщенные функции-конструкторы:
func NewContainer[T any]() *Container[T] {
return &Container[T]{Items: []T{}}
}
func (c *Container[T]) Add(item T) {
c.Items = append(c.Items, item)
}Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Прочитать тип элемента
scanner.Scan()
itemType := scanner.Text()
// Прочитать количество
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: Создать Container[string], используя NewContainer
// TODO: Прочитать 'count' элементов (у каждого имя на одной строке, данные на следующей)
// TODO: Добавить каждый элемент в контейнер
// TODO: Вывести каждый элемент в формате "[Name]: [Data]"
// TODO: Вывести "Total items: [count]", используя метод Count
case "int":
// TODO: Создать Container[int], используя NewContainer
// TODO: Прочитать 'count' элементов (у каждого имя на одной строке, целочисленные данные на следующей)
// TODO: Добавить каждый элемент в контейнер
// TODO: Вывести каждый элемент в формате "[Name]: [Data]"
// TODO: Вывести "Total items: [count]", используя метод Count
case "priced":
// TODO: Создать слайс для хранения экземпляров PricedItem[float64]
// TODO: Прочитать 'count' элементов (у каждого имя, количество и цена на отдельных строках)
// TODO: Создать PricedItem[float64] для каждого и добавить в слайс
// TODO: Вывести каждый элемент в формате "[Name] x[Quantity] @ [Price]" с ценой, отображающей один десятичный знак
// TODO: Вывести "Total items: [count]"
}
_ = scanner // Использовать scanner для чтения ввода
_ = count // Использовать count для цикла
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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