在case类值上强制实施约束的正确方法是什么?

时间:2014-06-02 20:09:16

标签: scala

在构建时抛出异常通常被视为不太礼貌的事情,但我不确定在尝试对case case值强制执行约束时如何避免这种情况。

比如说我需要表示一个范围,并且两个边界必须是正数。立即实施将是:

case class Range(from: Int, to: Int)

但是,这并不能确保fromto都是正面的,to也不会大于from

我的第一直觉是按如下方式实施:

case class Range(from: Int, to: Int) {
  require(from >= 0)
  require(to >= 0)
  require(to >= from)
}

然而,这会使Range的构造函数不安全。

是否存在一种通用模式来保持案例类的易用性,强制实施值约束并避免抛出异常?

3 个答案:

答案 0 :(得分:9)

这是非常主观的,但我会尝试。

这取决于你的班级是如何构建的。对于在内部代码中随意使用的简单实用程序范围类,任何使用它的程序员都应该知道,输入范围错误的值可能会导致奇怪的结果。你可以假设这些课程的理智使用确实没有遇到这个问题,并且在这些极少数情况下抛出异常可能并不是一个不好的做法,表明有人真的搞砸了。如果从背景中可以明显看出应该给课程带来什么样的价值观,那么如果人们不能满足这些理智的期望,我不会认为抛出异常是特别糟糕的。

将此与有时在Scala中仍然发生的NullPointerExceptionArithmethicException的行为进行比较。当面对足够的“疯狂”时,有时候进行防御性打算是不合理的。

另一方面,如果您的课程由值控制较少的值填充,即它们是用户输入的直接后果,则您必须更好地控制您的构造。使用返回OptionEither

的函数创建伴随对象
 object Range {
     def createSafe(from: Int, to: Int): Option[Range] = {
         if(from >= 0 && to >= 0 && to >= from)
            Some(Range(from, to))
         else
            None
     }
 }

然后,您可以通过覆盖apply作为建议的其他答案,在案例类的实例化中重用验证逻辑。

 object Range {
    def createSafe ...
    def apply(from: Int, to: Int): Range = {
        createSafe(from, to).getOrElse(throw new IllegalArgumentException(...))
    }
 }

答案 1 :(得分:3)

您可以重载随播对象中的apply运算符以实现您想要执行的操作:

object Range{
  def apply(from: Int, to: Int) ={
    val _from = Math.max(0, from)
    val _to = Math.max(0, to)
    if(_from < _to) new Range(_from, _to)
    else new Range(_to, _from)
  }
}

请注意,使用to作为变量名称可能会产生一些“有趣”的结果。

答案 2 :(得分:-1)

您需要合并类型编程。不幸的是,没有简短的答案。不过,我最近在博客上写了一个关于这个主题的系列文章,我认为可以帮助您解决这类问题:http://proseand.co.nz/2014/02/17/type-programming-shifting-from-values-to-types/