Чи є вказівки щодо найкращої практики щодо використання класів справ (або об'єктів регістру) проти розширення перерахування в Scala?
Вони, здається, пропонують деякі такі ж переваги.
enum
(для середини 2020 року).
Чи є вказівки щодо найкращої практики щодо використання класів справ (або об'єктів регістру) проти розширення перерахування в Scala?
Вони, здається, пропонують деякі такі ж переваги.
enum
(для середини 2020 року).
Відповіді:
Важлива відмінність полягає в тому, Enumeration
що приходять з підтримкою для створення їх з якоїсь name
струни. Наприклад:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Тоді ви можете зробити:
val ccy = Currency.withName("EUR")
Це корисно при бажанні зберегти перерахування (наприклад, до бази даних) або створити їх з даних, що знаходяться у файлах. Однак, я вважаю, що перерахування трохи незграбні в Scala і відчувають незручне доповнення, тому я зараз схильний використовувати case object
s. A case object
є більш гнучким, ніж enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Тож тепер я маю перевагу ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Як вказував @ chaotic3quilibrium (з деякими виправленнями для полегшення читання):
Що стосується шаблону "UnknownCurrency (code)", існують й інші способи впоратися з не знаходженням рядка коду валюти, ніж "розрив" закритого набору
Currency
типу типу. ТеперUnknownCurrency
типиCurrency
можуть проникнути в інші частини API.Доцільно висунути цей випадок назовні
Enumeration
і змусити клієнта мати справу зOption[Currency]
типом, який чітко вказуватиме на те, що існує дійсно відповідна проблема та "заохочувати" користувача API розібратися в цьому.
Для подальшого вивчення інших відповідей тут основними недоліками case object
s over Enumeration
s є:
Неможливо повторити всі екземпляри "перерахування" . Це, звичайно, так, але на практиці я вважаю надзвичайно рідкісним, що це потрібно.
Неможливо легко встановити дані про збережене значення . Це також справедливо, але, за винятком величезних перерахувань (наприклад, усіх валют), це не має великих витрат.
trade.ccy
в запечатаному прикладі ознаки.
case
object
генерувати більший (~ 4х) кодовий слід, ніж Enumeration
? Корисна відмінність особливо для scala.js
проектів, які потребують невеликого сліду.
ОНОВЛЕННЯ: Створено нове рішення на основі макросу, яке набагато перевершує рішення, яке я окреслюю нижче. Я настійно рекомендую використовувати це нове рішення на основі макросу . І, схоже, плани Dotty зроблять цей стиль рішення перерахуванням частиною мови. Whoohoo!
Резюме:
Існує три основні схеми спроби відтворення Java Enum
в рамках проекту Scala. Два з трьох візерунків; безпосередньо за допомогою Java Enum
та scala.Enumeration
не здатні включити вичерпне узгодження шаблону Scala. І третя; "запечатаний ознака + об'єкт регістру", робить ..., але має ускладнення ініціалізації класу / об'єкта JVM, що призводить до непослідовної генерації порядкового індексу.
Я створив рішення з двома класами; Перерахування та перерахуванняDecorated , розміщені в цій суті . Я не розміщував код у цій темі, оскільки файл для перерахунку був досить великий (+400 рядків - містить безліч коментарів, що пояснюють контекст реалізації).
Деталі:
питання, яке ви задаєте, досить загальне; "... коли використовувати case
класиobjects
проти розширення [scala.]Enumeration
". І виявляється, Є МНОГО можливих відповідей, кожна відповідь залежно від тонкощів конкретних вимог проекту. Відповідь можна звести до трьох основних моделей.
Для початку давайте переконаємось, що ми працюємо з тієї ж основної ідеї, що таке перерахування. Давайте визначимо перерахування здебільшого з точки зору Enum
наданого у Java 5 (1.5) :
Enum
, було б непогано мати можливість явно використовувати шаблон Scala, що відповідає вичерпності перевірки для перерахунку Далі розглянемо розміщені версії трьох найпоширеніших моделей рішення:
A) Насправді безпосередньо за допомогою шаблону JavaEnum
(у змішаному проекті Scala / Java):
public enum ChessPiece {
KING('K', 0)
, QUEEN('Q', 9)
, BISHOP('B', 3)
, KNIGHT('N', 3)
, ROOK('R', 5)
, PAWN('P', 1)
;
private char character;
private int pointValue;
private ChessPiece(char character, int pointValue) {
this.character = character;
this.pointValue = pointValue;
}
public int getCharacter() {
return character;
}
public int getPointValue() {
return pointValue;
}
}
Наступні елементи з визначення перерахування недоступні:
Для моїх поточних проектів я не маю переваги ризикувати навколо змішаного шляху Scala / Java. І навіть якщо я можу вибрати мішаний проект, пункт 7 вирішальний для того, щоб я міг вирішити питання про час збирання, якщо / коли я або додаю / видаляю члени перерахування, або пишу новий код для роботи з існуючими членами перерахування.
B) Використовуючи шаблон " sealed trait
+case objects
":
sealed trait ChessPiece {def character: Char; def pointValue: Int}
object ChessPiece {
case object KING extends ChessPiece {val character = 'K'; val pointValue = 0}
case object QUEEN extends ChessPiece {val character = 'Q'; val pointValue = 9}
case object BISHOP extends ChessPiece {val character = 'B'; val pointValue = 3}
case object KNIGHT extends ChessPiece {val character = 'N'; val pointValue = 3}
case object ROOK extends ChessPiece {val character = 'R'; val pointValue = 5}
case object PAWN extends ChessPiece {val character = 'P'; val pointValue = 1}
}
Наступні елементи з визначення перерахування недоступні:
Можна стверджувати, що він дійсно відповідає пунктам 5 та 6. визначення переліку. Для 5 - це розтягнення, щоб стверджувати, що це ефективно. Для 6, це не дуже просто розширити, щоб утримувати додаткові пов'язані дані однотонності.
C) Використовуючи scala.Enumeration
шаблон (натхненний цією відповіддю StackOverflow ):
object ChessPiece extends Enumeration {
val KING = ChessPieceVal('K', 0)
val QUEEN = ChessPieceVal('Q', 9)
val BISHOP = ChessPieceVal('B', 3)
val KNIGHT = ChessPieceVal('N', 3)
val ROOK = ChessPieceVal('R', 5)
val PAWN = ChessPieceVal('P', 1)
protected case class ChessPieceVal(character: Char, pointValue: Int) extends super.Val()
implicit def convert(value: Value) = value.asInstanceOf[ChessPieceVal]
}
Наступні елементи з визначення перерахування недоступні (трапляється, ідентичні списку для прямого використання Java Enum):
Знову ж таки для моїх поточних проектів, пункт 7 є критично важливим для того, щоб я міг вирішити питання про час збирання, якщо / коли я або додаю / видалю члени перерахування, або пишу якийсь новий код для роботи з існуючими членами перерахування.
Отже, з огляду на вищенаведене визначення перерахунку, жодне з вищезгаданих трьох рішень не працює, оскільки не забезпечує все, що зазначено у вищенаведеному визначенні перерахування:
Кожне з цих рішень може бути врешті перероблене / розширене / відновлене, щоб спробувати покрити деякі недоліки кожного з них. Однак ні Java, Enum
ні scala.Enumeration
рішення не можуть бути достатньо розширені, щоб забезпечити пункт 7. А для моїх власних проектів це одне з найбільш переконливих значень використання закритого типу в Scala. Я настійно віддаю перевагу компіляції попереджень / помилок, щоб вказати, що у мене є розрив / проблема в моєму коді, на відміну від необхідності виводити його з-за винятку / відмови під час виробництва.
У зв'язку з цим я розпочав роботу з case object
контуром, щоб побачити, чи можу я створити рішення, яке охоплювало б усе вищезазначене визначення. Перший виклик полягав у тому, щоб проникнути через ядро проблеми ініціалізації класу / об’єкта JVM (детально висвітлено в цій публікації StackOverflow ). І я нарешті змогла знайти рішення.
Як моє рішення - дві риси; Перерахування та перерахування декоровано , і оскільки ця Enumeration
ознака має довжину понад 400 рядків (багато коментарів, що пояснюють контекст), я пересилаю її в цю нитку (що б змусило її значно розтягнути сторінку). Для детальної інформації перейдіть безпосередньо до Суті .
Ось як виглядає рішення, використовуючи ту саму ідею даних, що і вище (повністю коментована версія доступна тут ) та реалізована в EnumerationDecorated
.
import scala.reflect.runtime.universe.{TypeTag,typeTag}
import org.public_domain.scala.utils.EnumerationDecorated
object ChessPiecesEnhancedDecorated extends EnumerationDecorated {
case object KING extends Member
case object QUEEN extends Member
case object BISHOP extends Member
case object KNIGHT extends Member
case object ROOK extends Member
case object PAWN extends Member
val decorationOrderedSet: List[Decoration] =
List(
Decoration(KING, 'K', 0)
, Decoration(QUEEN, 'Q', 9)
, Decoration(BISHOP, 'B', 3)
, Decoration(KNIGHT, 'N', 3)
, Decoration(ROOK, 'R', 5)
, Decoration(PAWN, 'P', 1)
)
final case class Decoration private[ChessPiecesEnhancedDecorated] (member: Member, char: Char, pointValue: Int) extends DecorationBase {
val description: String = member.name.toLowerCase.capitalize
}
override def typeTagMember: TypeTag[_] = typeTag[Member]
sealed trait Member extends MemberDecorated
}
Це приклад використання нової пари ознак перерахування, яку я створив (розташований у цьому розділі ) для реалізації всіх бажаних та окреслених можливостей у визначенні перерахування.
Висловлюється одна стурбованість тим, що імена членів перерахування повинні бути повторені ( decorationOrderedSet
у прикладі вище). Хоча я мінімізував це до одного повторення, я не міг зрозуміти, як зробити його ще менше через два питання:
getClass.getDeclaredClasses
має невизначений порядок (і навряд чи він буде в тому ж порядку, що і case object
декларації у вихідному коді)Враховуючи ці два питання, я повинен був відмовитися від спроби створити неявне замовлення, і я повинен був явно вимагати від клієнта визначення та декларації з якимсь впорядкованим набором понять. Оскільки в колекціях Scala немає впорядкованої реалізації набору, найкраще, що я міг зробити, це використовувати a, List
а потім час перевірки, чи справді це набір. Це не так, як я вважав би за краще цього досягти.
А з урахуванням конструкції потрібно цей другий список / набір впорядкованість val
, враховуючи ChessPiecesEnhancedDecorated
наведений вище приклад, можна було додати , case object PAWN2 extends Member
а потім забудьте додати Decoration(PAWN2,'P2', 2)
до decorationOrderedSet
. Отже, є перевірка виконання, щоб переконатися, що список є не тільки набором, але містить ВСІ об’єкти регістру, які розширюють sealed trait Member
. Це була особлива форма рефлексії / макро пекло для пропрацювання.
Будь ласка, залишайте коментарі та / або відгуки про історію .
org.scalaolio.util.Enumeration
і org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Об'єкти Case вже повертають своє ім'я для своїх методів toString, тому передавати його окремо не потрібно. Ось версія, схожа на jho (зручні методи, опущені для стислості):
trait Enum[A] {
trait Value { self: A => }
val values: List[A]
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
val values = List(EUR, GBP)
}
Об’єкти ліниві; використовуючи vals, замість цього ми можемо скинути список, але треба повторити ім'я:
trait Enum[A <: {def name: String}] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed abstract class Currency(val name: String) extends Currency.Value
object Currency extends Enum[Currency] {
val EUR = new Currency("EUR") {}
val GBP = new Currency("GBP") {}
}
Якщо ви не заперечуєте проти обману, ви можете попередньо завантажити свої значення перерахування за допомогою API-інтерфейсу відображення або чогось на зразок Google Reflections. Неліниві об'єкти випадку дають вам найчистіший синтаксис:
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency
case object GBP extends Currency
}
Приємно і чисто, з усіма перевагами кейсів і перелічень Java. Особисто я визначаю значення перерахунку поза об'єктом, щоб краще відповідати ідіоматичному коду Scala:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, я отримую лише значення, до яких раніше звертався. Чи є щось подібне?
Переваги використання класів тестів перед перерахуваннями:
Перевагами використання перерахунків замість класів регістрів є:
Тож загалом, якщо вам просто потрібен список простих констант за назвою, використовуйте перерахування. В іншому випадку, якщо вам потрібно щось трохи складніше або ви хочете, щоб компілятор додаткової безпеки повідомив, чи є у вас вказані всі збіги, використовуйте класи справ.
ОНОВЛЕННЯ: Код нижче містить помилку, описану тут . Нижче наведена програма тестування працює, але якщо ви використовували DayOfWeek.Mon (наприклад) перед самим DayOfWeek, вона не вдасться, оскільки DayOfWeek не був ініціалізований (використання внутрішнього об'єкта не викликає ініціалізацію зовнішнього об'єкта). Ви все одно можете використовувати цей код, якщо ви робите щось на кшталт val enums = Seq( DayOfWeek )
свого основного класу, змушуючи ініціалізувати ваші перерахунки, або ви можете використовувати модифікації хаотичного3-рівноваги. Чекаємо перерахунку на основі макросу!
Якщо хочете
то наступне може представляти інтерес. Зворотній зв'язок Ласкаво просимо.
У цій реалізації є абстрактні базові класи Enum та EnumVal, які ви розширюєте. Ми побачимо ці заняття через хвилину, але спочатку ось як би ви визначили перерахунок:
object DayOfWeek extends Enum {
sealed abstract class Val extends EnumVal
case object Mon extends Val; Mon()
case object Tue extends Val; Tue()
case object Wed extends Val; Wed()
case object Thu extends Val; Thu()
case object Fri extends Val; Fri()
case object Sat extends Val; Sat()
case object Sun extends Val; Sun()
}
Зауважте, що вам потрібно використовувати кожне значення enum (називати його метод застосування), щоб реалізувати його. [Я хочу, щоб внутрішні об'єкти не лінувалися, якщо я спеціально не прошу їх. Я думаю.]
Ми, звичайно, можемо додати методи / дані до DayOfWeek, Val або окремих об'єктів випадку, якщо цього захочемо.
А ось як би ти скористався такою перерахунком:
object DayOfWeekTest extends App {
// To get a map from Int id to enum:
println( DayOfWeek.valuesById )
// To get a map from String name to enum:
println( DayOfWeek.valuesByName )
// To iterate through a list of the enum values in definition order,
// which can be made different from ID order, and get their IDs and names:
DayOfWeek.values foreach { v => println( v.id + " = " + v ) }
// To sort by ID or name:
println( DayOfWeek.values.sorted mkString ", " )
println( DayOfWeek.values.sortBy(_.toString) mkString ", " )
// To look up enum values by name:
println( DayOfWeek("Tue") ) // Some[DayOfWeek.Val]
println( DayOfWeek("Xyz") ) // None
// To look up enum values by id:
println( DayOfWeek(3) ) // Some[DayOfWeek.Val]
println( DayOfWeek(9) ) // None
import DayOfWeek._
// To compare enums as ordinals:
println( Tue < Fri )
// Warnings about non-exhaustive pattern matches:
def aufDeutsch( day: DayOfWeek.Val ) = day match {
case Mon => "Montag"
case Tue => "Dienstag"
case Wed => "Mittwoch"
case Thu => "Donnerstag"
case Fri => "Freitag"
// Commenting these out causes compiler warning: "match is not exhaustive!"
// case Sat => "Samstag"
// case Sun => "Sonntag"
}
}
Ось що ви отримуєте при складанні:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Ви можете замінити "day match" на "(day: @uncked) match" там, де ви не хочете таких попереджень, або просто включіть випадок "загальний вигляд" в кінці.
Запустивши вищевказану програму, ви отримаєте такий результат:
Map(0 -> Mon, 5 -> Sat, 1 -> Tue, 6 -> Sun, 2 -> Wed, 3 -> Thu, 4 -> Fri)
Map(Thu -> Thu, Sat -> Sat, Tue -> Tue, Sun -> Sun, Mon -> Mon, Wed -> Wed, Fri -> Fri)
0 = Mon
1 = Tue
2 = Wed
3 = Thu
4 = Fri
5 = Sat
6 = Sun
Mon, Tue, Wed, Thu, Fri, Sat, Sun
Fri, Mon, Sat, Sun, Thu, Tue, Wed
Some(Tue)
None
Some(Thu)
None
true
Зауважте, що оскільки Список і Карти незмінні, ви можете легко видалити елементи для створення підмножин, не порушуючи саму перерахунок.
Ось сам клас Enum (і EnumVal всередині нього):
abstract class Enum {
type Val <: EnumVal
protected var nextId: Int = 0
private var values_ = List[Val]()
private var valuesById_ = Map[Int ,Val]()
private var valuesByName_ = Map[String,Val]()
def values = values_
def valuesById = valuesById_
def valuesByName = valuesByName_
def apply( id : Int ) = valuesById .get(id ) // Some|None
def apply( name: String ) = valuesByName.get(name) // Some|None
// Base class for enum values; it registers the value with the Enum.
protected abstract class EnumVal extends Ordered[Val] {
val theVal = this.asInstanceOf[Val] // only extend EnumVal to Val
val id = nextId
def bumpId { nextId += 1 }
def compare( that:Val ) = this.id - that.id
def apply() {
if ( valuesById_.get(id) != None )
throw new Exception( "cannot init " + this + " enum value twice" )
bumpId
values_ ++= List(theVal)
valuesById_ += ( id -> theVal )
valuesByName_ += ( toString -> theVal )
}
}
}
Ось більш досконале його використання, яке контролює ідентифікатори та додає дані / методи до абстракції Val та до самої перерахування:
object DayOfWeek extends Enum {
sealed abstract class Val( val isWeekday:Boolean = true ) extends EnumVal {
def isWeekend = !isWeekday
val abbrev = toString take 3
}
case object Monday extends Val; Monday()
case object Tuesday extends Val; Tuesday()
case object Wednesday extends Val; Wednesday()
case object Thursday extends Val; Thursday()
case object Friday extends Val; Friday()
nextId = -2
case object Saturday extends Val(false); Saturday()
case object Sunday extends Val(false); Sunday()
val (weekDays,weekendDays) = values partition (_.isWeekday)
}
var
] є прикордонним смертним гріхом у світі ПП" - я не думаю, що думка є загальновизнаною.
У мене є хороший простий ліб, який дозволяє використовувати запечатані риси / класи як значення перерахунків, не потребуючи ведення власного списку значень. Він спирається на простий макрос, який не залежить від баггі knownDirectSubclasses
.
Оновлення березня 2017 року: як коментує Ентоні Аксьолі , scala.Enumeration/enum
PR закрито.
Dotty (компілятор наступного покоління для Scala) візьме на себе місце, хоча дотти випуску 1970 та PR 1958 Мартіна Одерського .
Примітка: зараз (серпень 2016 року, 6+ років пізніше) є пропозиція про видалення scala.Enumeration
: PR 5352
Вимкнути
scala.Enumeration
, додати@enum
анотаціюСинтаксис
@enum
class Toggle {
ON
OFF
}
є можливим прикладом реалізації, наміром є також підтримка ADT, які відповідають певним обмеженням (без вкладень, рекурсії чи змінних параметрів конструктора), наприклад:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Принижує незручне катастрофу, яка є
scala.Enumeration
.Переваги @enum над scala. Перерахунок:
- Насправді працює
- Java interop
- Жодних проблем зі стиранням
- Не заплутаний mini-DSL, щоб дізнатися при визначенні перерахувань
Недоліки: немає.
Це вирішує питання про неможливість мати одну кодову базу, яка підтримує Scala-JVM
Scala.js
та Scala-Native (вихідний код Java не підтримуєтьсяScala.js/Scala-Native
, вихідний код Scala не в змозі визначити перерахунки, які приймаються існуючими API в Scala-JVM).
Ще один недолік класів справ у порівнянні з перерахуваннями, коли вам потрібно буде повторити чи фільтрувати всі екземпляри. Це вбудована здатність перерахування (і Java перераховує), в той час як класи справ автоматично не підтримують таку можливість.
Іншими словами: "не існує простого способу отримати список загального набору перерахованих значень за допомогою класів".
Якщо ви серйозно ставитесь до збереження сумісності з іншими мовами JVM (наприклад, Java), то найкращим варіантом є написання переліків Java. Вони працюють прозоро як від Scala, так і від Java-коду, що більше, ніж можна сказати, scala.Enumeration
або для об'єктів case. Не будемо мати нової бібліотеки перерахунків для кожного нового проекту хобі на GitHub, якщо цього можна уникнути!
Я бачив різні версії того, щоб зробити клас класу, що імітує перерахування. Ось моя версія:
trait CaseEnumValue {
def name:String
}
trait CaseEnum {
type V <: CaseEnumValue
def values:List[V]
def unapply(name:String):Option[String] = {
if (values.exists(_.name == name)) Some(name) else None
}
def unapply(value:V):String = {
return value.name
}
def apply(name:String):Option[V] = {
values.find(_.name == name)
}
}
Що дозволяє побудувати класи справ, які виглядають так:
abstract class Currency(override name:String) extends CaseEnumValue {
}
object Currency extends CaseEnum {
type V = Site
case object EUR extends Currency("EUR")
case object GBP extends Currency("GBP")
var values = List(EUR, GBP)
}
Можливо, хтось може придумати кращий трюк, ніж просто додати кожен клас класів до списку, як я. Це було все, про що я міг придумати.
Я повертався вперед і назад на ці два варіанти останні кілька разів, коли мені були потрібні. До недавнього часу моїм уподобанням був варіант із запечатаною ознакою / об'єктом справи.
1) Декларація перерахування Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Опечатані риси + об'єкти справи
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Хоча жодне з цих дій не відповідає всім тим, що дає вам перелік Java, нижче наведені плюси і мінуси:
Перерахунок Scala
Плюси: -Функції для інстанції з опцією або безпосередньо припущення точних (простіше при завантаженні з постійного магазину) -Ітерація над усіма можливими значеннями підтримується
Мінуси: -Попередження про компіляцію для невичерпного пошуку не підтримується (робить узгодження шаблону менш ідеальним)
Об'єкти справи / Опечатані ознаки
Плюси: - Використовуючи запечатані ознаки, ми можемо попередньо встановити деякі значення, тоді як інші можна вводити під час створення -повна підтримка відповідності шаблонів (застосовувати / не застосовувати методи, визначені)
Мінуси: - Введення даних із постійного магазину - вам часто доводиться використовувати відповідність шаблонів тут або визначати свій власний список усіх можливих «значень перерахувань»
Що зрештою змусило мене змінити свою думку, було щось на зразок наступного фрагмента:
object DbInstrumentQueries {
def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
val symbol = rs.getString(tableAlias + ".name")
val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
val pointsValue = rs.getInt(tableAlias + ".points_value")
val instrumentType = InstrumentType.fromString(rs.getString(tableAlias +".instrument_type"))
val productType = ProductType.fromString(rs.getString(tableAlias + ".product_type"))
Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
}
}
object InstrumentType {
def fromString(instrumentType: String): InstrumentType = Seq(CurrencyPair, Metal, CFD)
.find(_.toString == instrumentType).get
}
object ProductType {
def fromString(productType: String): ProductType = Seq(Commodity, Currency, Index)
.find(_.toString == productType).get
}
Ці .get
виклики були огидні - з допомогою перерахування замість цього я можу просто викликати метод withName на перерахування наступним чином :
object DbInstrumentQueries {
def instrumentExtractor(tableAlias: String = "s")(rs: ResultSet): Instrument = {
val symbol = rs.getString(tableAlias + ".name")
val quoteCurrency = rs.getString(tableAlias + ".quote_currency")
val fixRepresentation = rs.getString(tableAlias + ".fix_representation")
val pointsValue = rs.getInt(tableAlias + ".points_value")
val instrumentType = InstrumentType.withNameString(rs.getString(tableAlias + ".instrument_type"))
val productType = ProductType.withName(rs.getString(tableAlias + ".product_type"))
Instrument(symbol, fixRepresentation, quoteCurrency, pointsValue, instrumentType, productType)
}
}
Тому я вважаю, що моїм перевагою вперед є використання перерахунків, коли значення мають бути доступними з сховища та об'єктами регістрів / запечатаними ознаками в іншому випадку.
Я віддаю перевагу case objects
(це питання особистих уподобань). Щоб впоратися з проблемами, притаманними цьому підходу (проаналізуйте рядок і повторіть всі елементи), я додав кілька рядків, які не є ідеальними, але ефективними.
Я вставляю вам код, очікуючи, що він може бути корисним, а також, щоб інші могли його покращити.
/**
* Enum for Genre. It contains the type, objects, elements set and parse method.
*
* This approach supports:
*
* - Pattern matching
* - Parse from name
* - Get all elements
*/
object Genre {
sealed trait Genre
case object MALE extends Genre
case object FEMALE extends Genre
val elements = Set (MALE, FEMALE) // You have to take care this set matches all objects
def apply (code: String) =
if (MALE.toString == code) MALE
else if (FEMALE.toString == code) FEMALE
else throw new IllegalArgumentException
}
/**
* Enum usage (and tests).
*/
object GenreTest extends App {
import Genre._
val m1 = MALE
val m2 = Genre ("MALE")
assert (m1 == m2)
assert (m1.toString == "MALE")
val f1 = FEMALE
val f2 = Genre ("FEMALE")
assert (f1 == f2)
assert (f1.toString == "FEMALE")
try {
Genre (null)
assert (false)
}
catch {
case e: IllegalArgumentException => assert (true)
}
try {
Genre ("male")
assert (false)
}
catch {
case e: IllegalArgumentException => assert (true)
}
Genre.elements.foreach { println }
}
Для тих, хто все ще шукає, як змусити відповісти GatesDa на роботу : Ви можете просто посилатись на об'єкт справи після того, як оголосив його про своє ім'я:
trait Enum[A] {
trait Value { self: A =>
_values :+= this
}
private var _values = List.empty[A]
def values = _values
}
sealed trait Currency extends Currency.Value
object Currency extends Enum[Currency] {
case object EUR extends Currency;
EUR //THIS IS ONLY CHANGE
case object GBP extends Currency; GBP //Inline looks better
}
Я думаю , що найбільша перевага мати case classes
більш enumerations
, що ви можете використовувати шаблон класу типу аки однорангового поліморфізму . Не потрібно відповідати перелікам на зразок:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
натомість у вас буде щось на кшталт:
def someCode[SomeCaseClass](implicit val maker: Maker[SomeCaseClass]){
maker.make()
}
implicit val makerA = new Maker[CaseClassA]{
def make() = ...
}
implicit val makerB = new Maker[CaseClassB]{
def make() = ...
}