Ответ
Подход Go к обобщенному программированию эволюционировал. Исторически использовались интерфейсы, но с версии 1.18 появились полноценные дженерики. Вот основные подходы:
-
Дженерики (Generics, с Go 1.18) — наиболее современный и типобезопасный способ. Позволяет писать функции и структуры данных, работающие с любым типом, который удовлетворяет заданным ограничениям (constraints).
// Эта функция работает со слайсом любого типа T // и функцией, преобразующей T в U. func Map[T, U any](collection []T, f func(T) U) []U { result := make([]U, len(collection)) for i, item := range collection { result[i] = f(item) } return result }
-
Интерфейсы (
interface
) — классический механизм полиморфизма в Go. Тип удовлетворяет интерфейсу неявно, если реализует все его методы. Это позволяет писать код, работающий с любым типом, который реализует определенное поведение.// Функция Copy работает с любыми типами, // удовлетворяющими интерфейсам io.Writer и io.Reader. func Copy(dst io.Writer, src io.Reader) (int64, error)
-
Пустой интерфейс (
interface{}
) — исторический способ эмуляции дженериков до версии 1.18.interface{}
может содержать значение любого типа, но для работы с ним требуется проверка типа (type assertion), что небезопасно и менее производительно.func PrintAnything(v interface{}) { // Требуется проверка типа, чтобы безопасно использовать значение if str, ok := v.(string); ok { fmt.Println("Это строка:", str) } }
-
Рефлексия (
reflect
) — мощный, но медленный и сложный механизм для работы с типами в рантайме. Используется в крайних случаях, например, при создании фреймворков или сериализаторов, когда типы неизвестны на этапе компиляции.
Итог: Дженерики являются предпочтительным выбором для обобщенного программирования. Интерфейсы остаются ключевым инструментом для полиморфизма и определения поведений. Пустой интерфейс и рефлексию следует использовать с осторожностью.