Согласованный способ проверки класса в Scala с использованием Scalaz 7

Моя цель — проверить поля User в applyметоде object перед созданием одного эффективного экземпляра User:

case class User(String userName, String password)

object User {

  def apply(userValidator: UserValidator): ValidationNel[UserCreationFailure, User] = {
    //call UserValidator's validate() method here and initialize effective User instance.
  }

}

Я решил использовать Validation из Scalaz7 для накопления потенциальных недопустимых аргументов/ошибок.

Одним из недостатков следующего кода является то, что Scalaz7 API вынуждает меня сделать так, чтобы валидатор сам создавал экземпляр. Однако, следуя принципу единоличной ответственности, это явно не его роль. Его роль будет состоять в том, чтобы просто проверять поля и возвращать список некоторых ошибок.

Давайте сначала представим мой фактический код (для информации, Empty**** объекты - это просто некоторые case object расширения UserCreationFailure):

class UserValidator(val userName: String, val password: String)
  extends CommonValidator[UserCreationFailure] {

  def validate(): ValidationNel[UserCreationFailure, User] = {
    (checkForUserName ⊛
      checkForPassword)((userName, password) => new User(userName, password)
  }

  private def checkForUserName: ValidationNel[UserCreationFailure, String] = {
    checkForNonEmptyString(userName) {
      EmptyUserName
    }
  }

  def checkForPassword: ValidationNel[UserCreationFailure, String] = {
    checkForNonEmptyString(password) {
      EmptyPassword
    }
  }
}

Я ожидаю, что просто верну этот код фрагмента:

(checkForUserName ⊛ checkForPassword)

и привнесите соответствующий результат в мой класс User, что позволит создать эффективный экземпляр, выполнив следующие действия:

def apply(userValidator: UserValidator): ValidationNel[UserCreationFailure, User] = {
        userValidator(username, password).validate()((userName, password)(new User(userName, password))
 }

Действительно, с SRP было бы дружнее.

Но (checkForUserName ⊛ checkForPassword) возвращает тип полностью private:

private[scalaz] trait ApplicativeBuilder[M[_], A, B],

таким образом, у меня нет руки на возвращаемый тип class.

Поэтому я вынужден напрямую связывать с ним творение Пользователя.

Как я могу сохранить SRP и сохранить этот механизм проверки?

-----ОБНОВЛЕНИЕ----

Как упомянул @Travis Brown, намерение использовать внешний class для моего UserValidator может показаться странным. На самом деле, я ожидаю, что валидатор будет макетным, и поэтому я вынужден использовать композицию поверх trait/abstract class.


person Mik378    schedule 05.05.2013    source источник


Ответы (1)


Я не уверен, что понимаю, зачем вообще нужен выделенный класс UserValidator. В таком случае я бы с большей вероятностью объединил весь мой общий код проверки в отдельный трейт, а мой объект-компаньон User (или любой другой элемент, который я хочу нести ответственность за создание экземпляров User) расширит этот трейт. Вот быстрый набросок:

import scalaz._, Scalaz._

trait Validator[E] {
  def checkNonEmpty(error: E)(s: String): ValidationNel[E, String] =
    if (s.isEmpty) error.failNel else s.successNel
}

sealed trait UserCreationFailure
case object EmptyPassword extends UserCreationFailure
case object EmptyUsername extends UserCreationFailure

case class User(name: String, pass: String)

object User extends Validator[UserCreationFailure] {
  def validated(
    name: String,
    pass: String
  ): ValidationNel[UserCreationFailure, User] = (
    checkNonEmpty(EmptyUsername)(name) |@| checkNonEmpty(EmptyPassword)(pass)
  )(apply)
}

А потом:

scala> println(User.validated("", ""))
Failure(NonEmptyList(EmptyUsername, EmptyPassword))

scala> println(User.validated("a", ""))
Failure(NonEmptyList(EmptyPassword))

scala> println(User.validated("", "b"))
Failure(NonEmptyList(EmptyUsername))

scala> println(User.validated("a", "b"))
Success(User(a,b))

Если у вас есть огромное количество логики проверки, специфичной для User, и вы не хотите, чтобы она загрязняла ваш объект User, я полагаю, вы могли бы преобразовать ее в черту UserValidator, которая расширит ваш общий Validator и будет расширена на User.

person Travis Brown    schedule 05.05.2013
comment
Почему я хочу иметь дело с композицией вместо черты (наследования), потому что я ожидаю, что мой UserValidator будет издевательским. Вся цель состоит в том, чтобы предоставить mockable UserValidator, чтобы соответствовать моим модульным тестам относительно класса User. - person Mik378; 06.05.2013
comment
И, конечно же, мы не можем имитировать какую-либо черту/суперкласс одного точного класса. - person Mik378; 06.05.2013
comment
Мне нравится ваше решение, несмотря на то, что я думаю сохранить композицию, то есть с внешним классом UserValidator, но позволяя классу User выполнять соответствующие вызовы различных методов проверки, как вы представили. Большое спасибо :) - person Mik378; 06.05.2013