8

这更像是一个设计问题...

我真的很喜欢 Scala 的案例类并且经常使用它们。但是,我发现我经常将我的参数包含在Options(或者更确切地说,Lift's Boxes)中并设置默认值以允许灵活性并考虑到用户可能并不总是指定所有参数。我想我采用了这种做法。

我的问题是,这是一个合理的方法吗?鉴于一切都可能是可选的,可能会有很多样板和检查,以至于我想知道我是否不只是使用我的案例类,Map[String, Any]并且想知道我是否会更好地使用Map.

让我给你一个真实的例子。在这里,我正在模拟汇款:

case class Amount(amount: Double, currency: Box[Currency] = Empty)
trait TransactionSide
case class From(amount: Box[Amount] = Empty, currency: Box[Currency] = Empty, country: Box[Country] = Empty) extends TransactionSide
case class To(amount: Box[Amount] = Empty, currency: Box[Currency] = Empty, country: Box[Country] = Empty) extends TransactionSide
case class Transaction(from: From, to: To)

我认为比较容易理解。在这个最简单的情况下,我们可以Transaction这样声明:

val t = Transaction(From(amount=Full(Amount(100.0)), To(country=Full(US)))

我已经可以想象你认为它很冗长。如果我们指定所有内容:

val t2 = Transaction(From(Full(Amount(100.0, Full(EUR))), Full(EUR), Full(Netherlands)), To(Full(Amount(150.0, Full(USD))), Full(USD), Full(US)))

另一方面,尽管不得不Full到处乱扔,你仍然可以做一些很好的模式匹配:

t2 match {
  case Transaction(From(Full(Amount(amount_from, Full(currency_from1))), Full(currency_from2), Full(country_from)), To(Full(Amount(amount_to, Full(currency_to1))), Full(currency_to2), Full(country_to))) if country_from == country_to => Failure("You're trying to transfer to the same country!")
  case Transaction(From(Full(Amount(amount_from, Full(currency_from1))), Full(currency_from2), Full(US)), To(Full(Amount(amount_to, Full(currency_to1))), Full(currency_to2), Full(North_Korea))) => Failure("Transfers from the US to North Korea are not allowed!")
  case Transaction(From(Full(Amount(amount_from, Full(currency_from1))), Full(currency_from2), Full(country_from)), To(Full(Amount(amount_to, Full(currency_to1))), Full(currency_to2), Full(country_to))) => Full([something])
  case _ => Empty
}

这是一个合理的方法吗?使用 a 会更好地为我服务Map吗?或者我应该以不同的方式使用案例类吗?也许使用案例类的整个层次结构来表示具有不同指定信息量的交易?

4

2 回答 2

5

如果某件事真的是可选的,那么你真的别无选择。null不是一个选项(没有双关语)

但是,我强烈建议不要使用 Lift 的 box 类型,除非您需要它来专门处理 Lift API。你只是引入了不必要的依赖。

Amount我也会认真考虑一下没有指定货币是否真的有意义。如果它有效的,那么创建一个专用的“空对象”来表示一种未指定的货币会给你一个更干净的 API:

class LocalCurrency extends Currency

或者:

sealed trait Amount
case class LocalisedAmount(value: Double, currency: Currency) extends Amount
case class RawAmount(value: Double) extends Amount

对于TransactionSide子类,我觉得很奇怪你可以Currency单独指定Amount(它已经嵌入了货币的概念)。我赞成:

case class TxEnd(
    amount: Option[Amount] = None,
    country: Option[Country] = None)
case class Transaction(from: TxEnd, to: TxEnd)

最后...

是的,如果地图非常适合您的域,则使用地图,它们将使代码更简洁。

于 2011-07-01T14:04:15.597 回答
4

使用案例类不如地图灵活,因为您只能分配/访问预定义的字段。您需要事先构建完整的案例类层次结构。

另一方面,案例类提供了一种“编译时验证”,因为所有类型都是显式定义的(与 a 相比Map[String,Any]),并且您不能错误地分配/访问未指定的字段。案例类也应该更快,因为您不需要遍历地图哈希表来查找您要查找的内容。

“冗长”问题来自案例类的不可变方面,但对于不可变映射,您将遇到完全相同的问题。解决方案似乎是Lenses。这里有一个很好的谈话:

http://www.youtube.com/watch?v=efv0SQNde5Q&list=PLEDE5BE0C69AF6CCE

于 2011-07-01T13:30:46.560 回答