Использование тематических классов Scala в качестве карт De-facto - PullRequest
8 голосов
/ 01 июля 2011

Это больше вопрос дизайна, чем что-либо еще ...

Мне действительно нравятся тематические классы 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 ]

5 голосов
/ 01 июля 2011

Если что-то действительно необязательно, у вас действительно нет другого выбора. null - это , а не - опция (без каламбура).

Я бы настоятельно не рекомендовал бы использовать тип ящика Lift, за исключением случаев, когда он вам нужен для работы с API-интерфейсами Lift. Вы только вводите ненужную зависимость.

Я бы также серьезно подумал о том, имеет ли смысл иметь 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)

Наконец ...

Да, используйте карты, если они хорошо вписываются в ваш домен, они сделают код намного чище.

4 голосов
/ 01 июля 2011

Использование класса наблюдений менее гибко, чем карты, поскольку вы можете назначать / получать доступ только к предварительно определенным полям.Вам нужно будет заранее построить полную иерархию классов дел.

С другой стороны, класс дел предлагает своего рода «проверки во время компиляции», потому что все типы явно определены (в отличие от Map[String,Any]), и вы не можете назначить / получить доступ к не указанному полю по ошибке.Классы падежей также должны быть быстрее, потому что вам не нужно проходить по хеш-таблице карты, чтобы найти то, что вы ищете.

Проблема "многословия" исходит из неизменяемого аспекта классов падежей, но выточно такая же проблема с неизменяемыми картами.Решение, кажется, Линзы .Здесь очень приятно поговорить:

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

...