8

これは何よりも設計上の問題です...

私は 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か? または、ケース クラスを別の方法で使用する必要がありますか? ケースクラスの階層全体を使用して、異なる量の情報が指定されたトランザクションを表すのでしょうか?

4

2 に答える 2

5

何かが本当にオプションである場合、あなたは本当に他の選択肢がありません。オプションnullではありません(しゃれは意図されていません)。

ただし、Lift APIを特に処理するために必要な場合を除いて、Liftのボックスタイプの使用は強くお勧めします。不必要な依存関係を導入しているだけです。

Amountまた、特定の通貨がないことが本当に理にかなっているのかどうかについても真剣に考えたいと思います。有効な場合、指定されていない通貨を表す専用の「nullオブジェクト」を作成すると、よりクリーンな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指定できるのは奇妙だと思います。私は好きだと思います:CurrencyAmount

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]) 明示的に定義されており、指定されていないフィールドを誤って割り当て/アクセスできないためです。探しているものを見つけるためにマップ ハッシュ テーブルをトラバースする必要がないため、Case クラスも高速になるはずです。

「冗長性」の問題は、ケース クラスの不変の側面から生じますが、不変のマップでもまったく同じ問題が発生します。解決策はLensesのようです。ここにとてもいい話があります:

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

于 2011-07-01T13:30:46.560 に答える