Golang 1.18 представил поддержку дженериков, что позволяет разработчикам писать код, не зависящий от конкретных типов. Это означает, что теперь функции и типы могут быть написаны для работы с любым набором типов. В этой статье мы рассмотрим, как использовать дженерики Golang в структуре и с интерфейсами.
Использование обобщений Golang в структуре
Начиная с Go 1.18, вы можете определять универсальные типы:
type Model[T any] struct { Data []T }
Универсальный тип должен быть создан при использовании, а для создания экземпляра требуется список параметров типа. Вот пример:
func main() { // passing int as type parameter modelInt := Model[int]{Data: []int{1, 2, 3}} fmt.Println(modelInt.Data) // [1 2 3] // passing string as type parameter modelStr := Model[string]{Data: []string{"a", "b", "c"}} fmt.Println(modelStr.Data) // [a b c] }
Определить методы для универсальных типов
Если вы объявляете методы для универсального типа, вы должны повторить объявление параметра типа в приемнике, даже если параметры типа не используются в области действия метода. Например:
type Model[T any] struct { Data []T } func (m *Model[T]) Push(item T) { m.Data = append(m.Data, item) }
Использование интерфейсов с дженериками Golang
Типы на самом деле не реализуют универсальные интерфейсы, они реализуют экземпляры универсальных интерфейсов. Вы не можете использовать универсальный тип (включая интерфейсы) без создания экземпляра.
Вот пример:
type Getter[T any] interface { Get() T } type Model[T any] struct { Data []T } // implements Getter func (m *Model[T]) Get(i int) T { return m.Data[i] }
Объявите ограничение типа как интерфейс
В следующем разделе мы извлечем ранее определенное ограничение в его собственный интерфейс для повторного использования в нескольких местах. Использование интерфейсов в качестве ограничений типа позволяет любому реализующему типу удовлетворить ограничение. Например, если интерфейс имеет три метода и используется в качестве параметра типа в универсальной функции, все аргументы типа должны иметь эти методы. В этом разделе также рассматриваются интерфейсы ограничений, которые относятся к определенным типам. Пример:
type Number interface { int | float64 } type Getter[T Number] interface { Get() T } type Model[T Number] struct { Data []T } func (m *Model[T]) Push(item T) { m.Data = append(m.Data, item) } func (m *Model[T]) Get(i int) T { return m.Data[i] } func main() { // passing int as type parameter modelInt := Model[int]{Data: []int{1, 2, 3}} fmt.Println(modelInt.Data) // [1 2 3] // passing float64 as type parameter modelFloat := Model[float64]{Data: []float64{1.1, 2.2, 0.02}} fmt.Println(modelFloat.Data) // [1.1 2.2 0.02] modelInt.Push(4) fmt.Println(modelInt.Data) // [1 2 3 4] itemAtOne := modelFloat.Get(1) fmt.Println(itemAtOne) // 2.2 }
В этом примере, если мы попытаемся использовать тип, который не удовлетворяет Number
interface, вы получите ошибку компиляции.
_ = Model[string]{Data: []string{"a", "b"}} // string does not satisfy Number (string missing in int | float64)
В заключение, дженерики Golang предоставляют разработчикам возможность писать код, не зависящий от конкретных типов. Это позволяет использовать более повторно используемый и поддерживаемый код.