简体   繁体   English

使用Scalaz 7在Scala中验证类的有效方法

[英]Cohesive way to validate a class in Scala using Scalaz 7

My goal is to validate User 's fields within the object 's apply method before creating one effective User instance: 我的目标是在创建一个有效的User实例之前验证objectapply方法中的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.
  }

}

I chose to use Validation from Scalaz7 to accumulate potential illegal arguments / errors. 我选择使用Scalaz7中的 Validation来累积潜在的非法参数/错误。

One drawback in the following code is that Scalaz7 API force me to make the validator creates itself the instance. 以下代码的一个缺点是Scalaz7 API迫使我使验证器自己创建实例。 However, by following Single-Responsibility principle , it's clearly not its role. 但是,遵循单一责任原则 ,显然不是它的作用。 Its role would be to just validate fields and to return some errors list. 它的作用是仅验证字段并返回一些错误列表。

Let's first present my actual code (for information, Empty**** objects are just some case object extending UserCreationFailure ): 让我们首先介绍我的实际代码(有关信息, Empty****对象只是扩展UserCreationFailure一些case object ):

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
    }
  }
}

What I would expect is to merely return this snippet code: 我期望的是只返回这段代码:

(checkForUserName ⊛ checkForPassword)

and bring the appropriate result into my User class, allowing to create the effective instance by doing: 并将适当的结果带入我的User类,允许通过执行以下操作来创建有效的实例:

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

Indeed, it would be more friendly with SRP. 实际上,SRP会更友好。

But (checkForUserName ⊛ checkForPassword) returns a totally private type type: 但是(checkForUserName ⊛ checkForPassword)返回一个完全private类型:

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

thus I don't have the hand on the type of class returned. 因此我没有关于返回class的类型。

Therefore, I am forced to directly associate User's creation with it. 因此,我被迫直接将用户的创建与其关联。

How could I keep SRP and keep this validation mechanism? 我怎样才能保留SRP并保持这种验证机制?

-----UPDATE ---- -----更新 ----

As @Travis Brown mentioned, the intent to use an external class for my UserValidator may seem weird. 正如@Travis Brown所提到的,为UserValidator使用外部class的意图可能看起来很奇怪。 Actually, I expect the validator to be mockable and thus, I'm forced to use composition over trait / abstract class . 实际上,我希望验证器是可模拟的,因此,我被迫使用组合优于trait / abstract class

I'm not sure I understand why you need a dedicated UserValidator class in the first place. 我不确定我是否理解为什么你需要一个专门的UserValidator类。 In a case like this I'd be more likely to bundle all of my generic validation code into a separate trait, and to have my User companion object (or whatever other piece I want to be responsible for creating User instances) extend that trait. 在这样的情况下,我更有可能将我的所有通用验证代码捆绑到一个单独的特征中,并让我的User伴侣对象(或者我想要负责创建User实例的任何其他部分)扩展该特征。 Here's a quick sketch: 这是一个快速草图:

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)
}

And then: 然后:

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))

If you have a huge amount of User -specific validation logic that you don't want polluting your User object, I suppose you could factor it out into a UserValidator trait that would extend your generic Validator and be extended by User . 如果您有大量User特定的验证逻辑,您不希望污染您的User对象,我想您可以将其分解为UserValidator特征,该特征将扩展您的通用Validator并由User扩展。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM