В этой статье я покажу, почему легче писать служебные функции/методы, используя Generic в Golang. Сама функция полезности — это функция, которая выполняет обычное поведение и может быть повторно использована в нескольких местах.
Универсальное программирование – это стиль компьютерного программирования, в котором алгоритмы записываются в терминах типов, которые должны быть определены позже, а затем при необходимости создаются экземпляры для конкретных типов, предоставляемых в качестве параметров.
Используя generic в Go, мы можем иметь кодовую базу с меньшим количеством дубликатов, потому что в моей кодовой базе было несколько дубликатов до того, как я использовал Generic. Особенно кодовая база, использующая Go версии 1.17 и ниже.
«Разговоры дешевы. Покажи мне код». ― Линус Торвальдс
Давайте взглянем на пример кода ниже.
func InArray(haystack []string, needle string) bool { for _, hay := range haystack { if hay == needle { return true } } return false }
Функция InArray предназначена для проверки существования элемента в массиве. На практике мне нужно дублировать функцию, если я проверяю массивы с другим типом данных.
func InArrayString(haystack []string, needle string) bool { for _, hay := range haystack { if hay == needle { return true } } return false } func InArrayInt(haystack []int, needle int) bool { for _, hay := range haystack { if hay == needle { return true } } return false } func InArrayFloat(haystack []float64, needle float64) bool { for _, hay := range haystack { if hay == needle { return true } } return false }
Если мы хотим удалить дублирование кода из кодовой базы без использования универсального, мы можем использовать библиотеку reflect, особенно если кодовая база по-прежнему использует Golang версии 1.17 или ниже.
func InArray(haystack any, needle any) (bool, error) { haystackValue := reflect.ValueOf(haystack) if haystackValue.Kind() != reflect.Slice && haystackValue.Kind() != reflect.Array { return false, errors.New("haystack is not an array") } needleValue := reflect.ValueOf(needle) for i := 0; i < haystackValue.Len(); i++ { val := haystackValue.Index(i) if val.Kind() == needleValue.Kind() && val.Interface() == needleValue.Interface() { return true, nil } } return false, nil }
Использование reflect может удалить дублирование в коде, но увеличит сложность функции. Но из-за затрат мы каким-то образом теряем возможность «статической типизации», потому что она должна проверять тип данных во время выполнения. Кроме того, нам нужно убедиться, что предоставленный ввод действителен, и обрабатывать ошибки при использовании функции.
Если вы все же хотите избежать усложнения кода, вы можете сначала преобразовать массив, а затем передать его служебной функции.
// Declaration func InArray(haystack []string, needle string) bool { for _, hay := range haystack { if hay == needle { return true } } return false } func main() { // Usage haystack := []int{1, 2, 3, 4, 5} needle := 1 haystackString := make([]string, 0, len(haystack)) for _, hay := range haystack { haystackString = append(haystackString, string(hay)) } fmt.Println(InArray(haystackString, string(needle))) }
При преобразовании вам необходимо преобразовывать массив каждый раз, когда тип массива отличается от аргумента InArray. Другими словами, при использовании этой служебной функции у вас все еще будет повторяющийся код. Кроме того, преобразование усложняет ваш код (временная сложность в приведенном выше коде).
Мы можем удалить дублирование и по-прежнему иметь простой код, используя Generic.
func InArray[T comparable](haystack []T, needle T) bool { for _, hay := range haystack { if hay == needle { return true } } return false }
Код с универсальным и без универсальным сначала похож, и вам не нужно добавлять больше сложности, как при использовании библиотеки reflect.
Generic подходит для создания функции полезности с более чем одним типом ввода. Но не злоупотребляйте этим.
Хорошо, это все. Также можно поиграть с кодом здесь. Спасибо.