Введение в Generics
Часть раздела Объектно-ориентированное программирование путешествия по GO на Coddy. Урок 67 из 107.
До Go 1.18 написание повторно используемого кода для разных типов означало либо дублирование функций, либо использование пустого интерфейса any с проверками типов. Обобщения решают эту проблему, позволяя писать функции и типы, работающие с несколькими типами и сохраняющие полную типобезопасность.
Представь, что нужно найти минимальное значение в срезе. Без обобщённых типов пришлось бы написать отдельные функции для каждого типа:
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
}С обобщениями вы пишете логику один раз, используя параметр типа в квадратных скобках:
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}) с одной и той же функцией. Компилятор проверяет типы во время компиляции, поэтому вы получаете безопасность без дополнительных затрат во время выполнения.
В следующих уроках мы подробно рассмотрим параметры типов, ограничения и обобщённые структуры.
Задание
ЛегкоДавайте создадим анализатор результатов, который работает и с целыми числами, и с числами с плавающей точкой, используя обобщения! Вы создадите одну обобщённую функцию, которая находит максимальное значение в срезе, избавляя от необходимости дублировать код для разных числовых типов.
Вы распределите код по двум файлам:
analyzer.go: Определите обобщённую функцию анализа.Создайте обобщённую функцию
Max[T int | float64](values []T) T, которая находит и возвращает максимальное значение в срезе. Функция должна работать и с типомint, и с типомfloat64благодаря параметру типаT.Также создайте обобщённую функцию
Sum[T int | float64](values []T) T, которая вычисляет и возвращает сумму всех значений в срезе.main.go: Считайте входные данные и продемонстрируйте работу обобщённых функций с разными типами.Считайте индикатор типа (
intилиfloat), затем считайте количество значений и столько же самих значений. Разберите значения в соответствии с типом, вызовите иMax, иSumс подходящим типом и выведите результаты.Для целочисленного ввода выведите:
Max (int): [value] Sum (int): [value]Для ввода чисел с плавающей точкой выведите результат с одним знаком после запятой:
Max (float): [value] Sum (float): [value]
Будут предоставлены следующие входные данные:
- Строка 1: индикатор типа (
intилиfloat) - Строка 2: количество значений (целое число)
- Следующие строки: по одному значению в каждой строке
Например, для следующих данных:
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Обратите внимание, как одни и те же обобщённые функции работают и с целыми числами, и с числами с плавающей точкой: параметр типа [T int | float64] позволяет компилятору создавать типобезопасный код для каждого использования, а вам достаточно написать логику всего один раз.
Попробуйте сами
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