Это case-класс, так почему бы вам не использовать сопоставление с образцом?
def validateConfig(config: Config): Try[Config] = config match {
case Config(None, _, _, _) => Failure(new IllegalArgumentException("Email Address")
case Config(_, None, _, _) => Failure(new IllegalArgumentException("First Name")
case Config(_, _, None, _) => Failure(new IllegalArgumentException("Last Name")
case Config(_, _, _, None) => Failure(new IllegalArgumentException("Password")
case _ => Success(config)
}
В вашем простом примере моим приоритетом было бы забыть монады и цепочки, просто избавиться от этого неприятного if...else
запаха.
Однако в то время как класс case отлично работает для короткого списка, для большого количества параметров конфигурации это становится утомительным и возрастает риск ошибки. В этом случае я бы рассмотрел что-то вроде этого:
- Добавьте метод, который возвращает карту ключей-> значений параметров конфигурации, используя имена параметров в качестве ключей.
- Попросите метод Validate проверить, является ли какое-либо значение на карте
None
- Если такого значения нет, вернуть успех.
- Если хотя бы одно значение совпадает, верните это имя значения с ошибкой.
Итак, если предположить, что где-то определено
type OptionMap = scala.collection.immutable.Map[String, Option[Any]]
и класс Config
имеет такой метод:
def optionMap: OptionMap = ...
то я бы написал Config.validate
так:
def validate: Either[List[String], OptionMap] = {
val badOptions = optionMap collect { case (s, None) => s }
if (badOptions.size > 0)
Left(badOptions)
else
Right(optionMap)
}
Итак, теперь Config.validate
возвращает либо Left
, содержащее имя всех неверных параметров, либо Right
, содержащее полную карту параметров и их значения. Честно говоря, вероятно, не имеет значения, что вы поместите в Right
.
Теперь все, что хочет проверить Config
, просто вызывает Config.validate
и проверяет результат. Если это Left
, он может выдать IllegalArgumentException
, содержащий одно или несколько названий неверных вариантов. Если это Right
, он может делать все, что захочет, зная, что Config
действителен.
Таким образом, мы могли бы переписать вашу функцию validateConfig
как
def validateConfig(config: Config): Try[Config] = config.validate match {
case Left(l) => Failure(new IllegalArgumentException(l.toString))
case _ => Success(config)
}
Вы видите, насколько более функциональным становится и объектно-ориентированный подход?
- Нет императивной цепочки
if...else
- Объект
Config
проверяет себя
- Последствия недопустимости объекта
Config
остаются на усмотрение более крупной программы.
Я думаю, что реальный пример был бы еще более сложным. Вы проверяете параметры, говоря: «Содержит ли он Option[String]
или None
?» но не проверяя действительность самой строки. Действительно, я думаю, что ваш класс Config
должен содержать карту опций, где имя сопоставляется со значением и с анонимной функцией, которая проверяет строку. Я мог бы описать, как расширить приведенную выше логику для работы с этой моделью, но я думаю, что оставлю это вам в качестве упражнения. Я дам вам подсказку: вы можете захотеть вернуть не только список неудачных вариантов, но и причину отказа в каждом случае.
Да, кстати... Я надеюсь, что ни один из вышеперечисленных не означает, что я думаю, что вы должны хранить параметры и их значения как optionMap
внутри объекта. Я думаю, что полезно иметь возможность извлекать их таким образом, но я бы никогда не поощрял такое раскрытие фактического внутреннего представления;)
person
itsbruce
schedule
12.09.2013
Config
является неизменяемым и изменяется путем копирования. Он начинается со значений по умолчанию для каждого поля, и они изменяются по мере анализа параметров. Мне нужно каким-то образом по умолчанию указать, что параметр не установлен;Option[String]
кажется намного лучше, чемnull
. Я не использую функциональностьrequired
в scopt, потому что хочу упростить тестирование отдельных параметров командной строки. - person Ralph   schedule 13.09.2013