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

pr1*_*001 8 scala map pattern-matching case-class

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

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

我的问题是,这是一种合理的方法吗?鉴于一切都可以是可选的,可以有很多样板和检查,到目前为止我是否想知道我是不是只是使用我的案例类,Map[String, Any]并且想知道我是否会因为使用a而变得更好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)
Run Code Online (Sandbox Code Playgroud)

我认为比较容易理解.在这个最简单的我们可能会声明Transaction如下:

val t = Transaction(From(amount=Full(Amount(100.0)), To(country=Full(US)))
Run Code Online (Sandbox Code Playgroud)

我已经可以想象你认为它很冗长.如果我们指定一切:

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)))
Run Code Online (Sandbox Code Playgroud)

另一方面,尽管不得不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
}
Run Code Online (Sandbox Code Playgroud)

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

Kev*_*ght 5

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

强烈建议不要使用Lift的盒子类型,除非你需要它专门处理Lift API.你只是引入了不必要的依赖.

我还要认真考虑一下Amount没有指定货币是否真的有意义.如果它有效的,然后创建一个专用的"空对象"来表示未指定的货币会给你一个更简洁的API:

class LocalCurrency extends Currency
Run Code Online (Sandbox Code Playgroud)

或者:

sealed trait Amount
case class LocalisedAmount(value: Double, currency: Currency) extends Amount
case class RawAmount(value: Double) extends Amount
Run Code Online (Sandbox Code Playgroud)

对于TransactionSide子类,我觉得奇怪的是你可以Currency单独指定Amount(已经嵌入了货币的概念).我愿意:

case class TxEnd(
    amount: Option[Amount] = None,
    country: Option[Country] = None)
case class Transaction(from: TxEnd, to: TxEnd)
Run Code Online (Sandbox Code Playgroud)

最后...

是的,如果地图适合您的域名,则使用地图,它们会使代码更清晰.

  • 当然,但我没有看到我对"Box"有任何好处.无论如何,我不想进一步深入到永恒的'Box` vs`Option` /'Either'Scala辩论中,所以让我们暂时留在那里.;-) (2认同)

par*_*tic 4

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

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

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

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