Введение в Generics
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 67 из 107.
До Go 1.18 написание повторно используемого кода для разных типов означало либо дублирование функций, либо использование пустого интерфейса any с проверками типов. Generics решают эту проблему, позволяя писать функции и типы, которые работают с несколькими типами, сохраняя при этом полную типобезопасность.
Рассмотрим поиск минимального значения в slice. Без generic потребовались бы отдельные функции для каждого типа:
func MinInt(values []int) int {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}
func MinFloat64(values []float64) float64 {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}С помощью обобщений вы один раз записываете логику, используя type parameter в квадратных скобках:
func Min[T int | float64](values []T) T {
min := values[0]
for _, v := range values {
if v < min {
min = v
}
}
return min
}Конструкция [T int | float64] объявляет параметр типа T, который может быть либо int, либо float64. Теперь вы можете вызвать Min([]int{3, 1, 2}) или Min([]float64{3.5, 1.2}) с помощью одной и той же функции. Компилятор проверяет типы во время компиляции, поэтому вы получаете безопасность без дополнительных затрат во время выполнения.
В следующих уроках мы подробно рассмотрим параметры типов, ограничения и generic-структуры.
Задание
ЛегкоДавайте создадим анализатор результатов, который работает как с целыми, так и с числами с плавающей точкой, используя generic! Вы создадите одну generic function, которая может находить maximum значение в slice, устраняя необходимость в дублировании кода для разных числовых типов.
Вы организуете код в двух файлах:
analyzer.go: Определите свою generic function для анализа.Создайте generic function
Max[T int | float64](values []T) T, которая находит и возвращает maximum значение в slice. Function должна работать как с типомint, так и с типомfloat64посредством type parameterT.Также создайте generic function
Sum[T int | float64](values []T) T, которая calculates и returns сумму всех values в slice.main.go: Считайте входные данные и продемонстрируйте свои generic functions с разными типами.Считайте indicator типа (
intилиfloat), затем считайте count, за которым следует соответствующее количество values. Разберите values в соответствии с типом, вызовите обе функцииMaxиSumс подходящим типом и выведите results.Для целочисленного ввода выведите:
Max (int): [value] Sum (int): [value]Для ввода с плавающей точкой выведите значение с одним десятичным знаком:
Max (float): [value] Sum (float): [value]
Будут предоставлены следующие входные данные:
- Строка 1: Type indicator (
intилиfloat) - Строка 2: Count values (integer)
- Следующие строки: По одному значению в каждой строке
Например, для следующих данных:
int
4
15
8
23
11Ваш вывод должен быть таким:
Max (int): 23
Sum (int): 57А для следующих данных:
float
3
4.5
9.2
6.8Ваш вывод должен быть таким:
Max (float): 9.2
Sum (float): 20.5Обратите внимание, как одни и те же generic functions обрабатывают и целочисленные данные, и данные с плавающей точкой: type parameter [T int | float64] позволяет компилятору генерировать type-safe код для каждого использования, а вы при этом пишете логику только один раз.
Попробуйте сами
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Прочитать индикатор типа
typeStr, _ := reader.ReadString('\n')
typeStr = strings.TrimSpace(typeStr)
// Прочитать количество
countStr, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countStr))
if typeStr == "int" {
// Прочитать целочисленные значения
values := make([]int, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.Atoi(strings.TrimSpace(line))
}
// TODO: Вызвать Max и Sum со срезом целых чисел
// TODO: Вывести результаты в формате "Max (int): [value]" и "Sum (int): [value]"
} else if typeStr == "float" {
// Прочитать значения с плавающей точкой
values := make([]float64, count)
for i := 0; i < count; i++ {
line, _ := reader.ReadString('\n')
values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
}
// TODO: Вызвать Max и Sum со срезом float64
// TODO: Вывести результаты с одним знаком после запятой в формате "Max (float): [value]" и "Sum (float): [value]"
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
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