Мне интересно, какие шаблоны следует использовать в Go в таких случаях. Итак, давайте предположим, что у вас есть структура Request
, содержащая некоторую информацию для выполнения HTTP-запроса, а также параметры, которые могут быть отправлены в полезных данных, и их соответствующие проверки. Теперь в зависимости от типа данных параметра будут применяться разные типы проверок, поэтому я решил использовать map[string]Validations
, где я мог бы хранить тип проверок, упомянутый в объединении Validations
(по сути, имея возможность вместить как IntValidations
, так и StrValidations
на той же карте).
Но приведенный ниже код приведет к ошибке, поскольку Validations
можно использовать только как параметр типа, так как же нам добиться этого в Go? В Java этого можно было бы легко достичь с помощью полиморфизма, где Validation
был бы родительским классом, а проверки int и float были бы дочерними классами, поэтому позже мы могли бы легко создать экземпляр HashMap
из Validations
.
Примечание. Я бы хотел избежать использования map[string]interface{}
.
type Validations interface {
IntValidations | StrValidations
}
type IntValidations struct {
MaxVal int
MinVal int
}
type StrValidations struct {
MinLength int
MaxLength int
Regex string
}
type Request struct {
Path string
Payload map[string]Validations // Error: Validations cannot be used outside of a type constraint.
}
Понятно, но как я смогу использовать это и заполнить структуры IntValidations и StrValidations этим... Не могли бы вы рассказать об этом немного подробнее? Вы предлагаете избавиться от этих двух структур и вместо этого выполнять проверки в методе Do?
Реализуйте методы Do
для типов IntValidation
и StrValidations
. См. Обзор Go для получения информации о том, как реализовать метод и как методы связаны с интерфейсами.
Как отметил @Cerise Limón в комментариях, вы можете сделать это вот так.
package main
type Validators interface {
Validate(value any) error // Can return bool also
}
type IntValidator struct {
MaxVal int
MinVal int
}
func (intValidator *IntValidator) Validate(value any) error {
// run validations
return nil
}
type StrValidator struct {
MinLength int
MaxLength int
Regex string
}
func (strValidator *StrValidator) Validate(value any) error {
// run validations
return nil
}
type Request struct {
Path string
Payload map[string]Validations
}
Теперь вы можете добавлять валидаторы с помощью
func main() {
request := Request{Payload: make(map[string]Validations)}
request.Payload["myIntVar"] = &IntValidations{MinVal: 10, MaxVal: 20}
request.Payload["myStrVar"] = &IntValidations{MinLen: 10, MaxLen: 15, Regex: "[a-z]+"}
if err := request.Payload["myIntVar"].Validate(13); err != nil {
panic("could not validate")
}
}
Отлично, это именно то, что я искал. Но еще один уточняющий вопрос. Что, если бы у меня был случай, когда хотелось бы создать аналогичную карту, содержащую несколько похожих типов, но в отличие от этого конкретного варианта использования использование такого метода, как Validate
, вообще не требуется. Как бы мы тогда достигли того же?
@ansme Опишите, что делает типы «похожими».
@CeriseLimón Скажем так, это карта map[string]Animal
, где мы могли бы хранить подобные типы в качестве значений, например type Cat struct
, type Dog struct
, где эти структуры просто содержат базовые поля, такие как name string
, neutered bool
и т. д.. но давайте предположим, что они не требуют любая функциональность... следовательно, базовый интерфейс Animal
не имеет определенных для него методов. Тогда как мы сможем создать map[string]Animal
?
@ansme Следуйте шаблону в этом ответе, но используйте пустой метод Validate
.
@asme, вы можете создать простой метод Validate с 0 аргументами, ничего не возвращать и реализовать его с пустым телом, чтобы указать, что он реализует интерфейс Validator, но ничего не делает.
@TimsibAdnap Вместо использования any
Есть ли способ реализовать метод validate
с аргументами int
, string
, специфичными для соответствующих структур реализации?
Да, вы можете использовать универсальный интерфейс. Но общие интерфейсы нельзя хранить на карте. Проверьте это. Я бы порекомендовал вам оставить его как any
, а внутри функции Validate
отметьте его как if intVal, ok := value.(int); !ok { return err }
Используйте
map[string]Validation
, гдеValidation
— это интерфейс с методами, подходящими для проверки. Примерtype Validation interface { Do(<insert args here>) error }