使用Scalaz 7在Scala中验证类的一种紧密方法

3

我的目标是在创建有效的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.
  }

}

我选择使用Scalaz7中的Validation来累积潜在的非法参数/错误。

以下代码的一个缺点是,Scalaz7 API强制我使验证器创建实例。然而,按照单一职责原则,显然这不是它的角色。它的角色应该只是验证字段并返回一些错误列表。

让我们先介绍我的实际代码(Empty****对象只是一些扩展UserCreationFailurecase 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
    }
  }
}

我会尽力为您提供帮助,以下是您需要翻译的内容:

我所期望的仅仅是返回这段代码片段:

(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可能看起来很奇怪。 实际上,我希望验证器可以被mockable,因此我被迫使用组合而不是trait/abstract class

1个回答

4

我不确定你为什么需要一个专门的UserValidator类。在这种情况下,我更倾向于将所有通用验证代码捆绑到单独的trait中,并且让我的User companion对象(或任何其他我想要负责创建User实例的部分)扩展该trait。以下是一个快速草图:

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

如果你有大量与用户相关的验证逻辑,而又不想污染你的用户对象,我认为你可以将其分解成一个名为UserValidator的特质(trait),它将扩展你的通用验证器(Validator),并被User对象继承。

我想使用组合而不是继承的原因是因为我希望我的UserValidator可以进行模拟。整个目标是提供一个可模拟的UserValidator,以适应关于User类的单元测试。 - Mik378
当然,我们不能模拟任何一个精确类的特征/超类。 - Mik378
我喜欢你的解决方案,尽管我认为保持组合更好,因此使用外部的UserValidator类,但让User类进行适当的不同验证方法调用,就像你所展示的那样。非常感谢 :) - Mik378

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接