使用Scala案例类作为事实上的地图

时间:2011-07-01 11:33:33

标签: scala map pattern-matching case-class

这是一个设计问题而不是其他任何事情......

我非常喜欢Scala的案例类并经常使用它们。但是,我发现我经常在Options(或者更确切地说,Lift 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
}

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

2 个答案:

答案 0 :(得分:5)

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

我会强烈建议不要使用Lift的盒子类型,除非你需要它专门处理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)

...最后

是的,如果地图适合您的网域,请使用地图,它们会使代码更清晰。

答案 1 :(得分:4)

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

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

“详细程度”问题来自案例类的不可变方面,但是对于不可变映射,您将遇到完全相同的问题。解决方案似乎是镜头。这里有一个非常好的谈话:

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