Esistono linee guida sulle migliori pratiche su quando utilizzare le classi case (o gli oggetti case) rispetto all'estensione dell'enumerazione in Scala?
Sembrano offrire alcuni degli stessi vantaggi.
enum
(per metà 2020).
Esistono linee guida sulle migliori pratiche su quando utilizzare le classi case (o gli oggetti case) rispetto all'estensione dell'enumerazione in Scala?
Sembrano offrire alcuni degli stessi vantaggi.
enum
(per metà 2020).
Risposte:
Una grande differenza è che viene Enumeration
fornito con il supporto per istanziarli da qualche name
stringa. Per esempio:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Quindi puoi fare:
val ccy = Currency.withName("EUR")
Ciò è utile quando si desidera conservare le enumerazioni (ad esempio in un database) o crearle dai dati che risiedono nei file. Tuttavia, trovo in generale che le enumerazioni siano un po 'goffe in Scala e abbiano la sensazione di un componente aggiuntivo imbarazzante, quindi ora tendo a usare case object
s. A case object
è più flessibile di un enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Quindi ora ho il vantaggio di ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Come ha sottolineato @ chaotic3quilibrium (con alcune correzioni per facilitare la lettura):
Per quanto riguarda il modello "UnknownCurrency (code)", esistono altri modi per gestire il non trovare una stringa di codice di valuta oltre a "spezzare" il tipo di set chiuso del
Currency
tipo.UnknownCurrency
essere di tipoCurrency
ora può intrufolarsi in altre parti di un'API.È consigliabile spingere quel caso all'esterno
Enumeration
e fare in modo che il client gestisca unOption[Currency]
tipo che indichi chiaramente che esiste davvero un problema di corrispondenza e "incoraggia" l'utente dell'API a risolverlo da solo.
Per dare seguito alle altre risposte qui, i principali svantaggi di case object
s su Enumeration
s sono:
Impossibile iterare su tutte le istanze dell '"enumerazione" . Questo è certamente il caso, ma ho trovato estremamente raro in pratica che ciò sia necessario.
Impossibile creare un'istanza facilmente dal valore persistente . Questo è anche vero ma, tranne nel caso di enormi enumerazioni (ad esempio, tutte le valute), ciò non presenta un enorme sovraccarico.
trade.ccy
nell'esempio tratto sigillato.
case
object
generano un ingombro di codice maggiore (~ 4x) di Enumeration
? Distinzione utile soprattutto per i scala.js
progetti che richiedono un ingombro ridotto.
AGGIORNAMENTO: è stata creata una nuova soluzione basata su macro che è di gran lunga superiore alla soluzione che ho delineato di seguito. Consiglio vivamente di utilizzare questa nuova soluzione basata su macro . E sembra che i piani per Dotty rendano questo stile di soluzione enum parte del linguaggio. Whoohoo!
Riepilogo:
Esistono tre schemi di base per tentare di riprodurre Java Enum
all'interno di un progetto Scala. Due dei tre modelli; usando direttamente Java Enum
e scala.Enumeration
, non sono in grado di abilitare l'esaustivo pattern matching di Scala. E il terzo; "tratto sigillato + oggetto case", ha ... ma presenta complicazioni di inizializzazione della classe / oggetto JVM che generano una generazione di indice ordinale incoerente.
Ho creato una soluzione con due classi; Enumerazione ed enumerazioneDecorated , situato in questo Gist . Non ho inserito il codice in questa discussione poiché il file per l'Enumerazione era piuttosto grande (+400 righe - contiene molti commenti che spiegano il contesto di implementazione).
Dettagli:
la domanda che stai ponendo è piuttosto generale; "... quando usare le case
classiobjects
contro l'estensione [scala.]Enumeration
". E si scopre che ci sono MOLTE possibili risposte, ciascuna risposta a seconda delle sottigliezze dei requisiti specifici del progetto che hai. La risposta può essere ridotta a tre schemi di base.
Per iniziare, assicuriamoci di lavorare dalla stessa idea di base di cosa sia un'enumerazione. Definiamo un'enumerazione principalmente in termini di Enum
forniti a partire da Java 5 (1.5) :
Enum
, sarebbe bello essere in grado di sfruttare esplicitamente il modello di Scala che combina l'esaustività controllando un'enumerazione Quindi, diamo un'occhiata alle versioni ridotte dei tre modelli di soluzione più comuni pubblicati:
A) In realtà direttamente usando ilEnum
pattern Java (in un progetto misto 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;
}
}
I seguenti elementi dalla definizione dell'enumerazione non sono disponibili:
Per i miei progetti attuali, non ho il vantaggio di assumermi i rischi legati al percorso dei progetti misti Scala / Java. E anche se potessi scegliere di fare un progetto misto, l'elemento 7 è fondamentale per consentirmi di rilevare problemi di tempo di compilazione se / quando aggiungo / rimuovo membri di enumerazione o sto scrivendo un nuovo codice per gestire i membri di enumerazione esistenti.
B) Utilizzando il modello " 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}
}
I seguenti elementi dalla definizione dell'enumerazione non sono disponibili:
È discutibile che soddisfi davvero gli elementi di definizione dell'enumerazione 5 e 6. Per 5, è un tratto affermare che sia efficiente. Per 6, non è davvero facile estenderlo per contenere ulteriori dati di singolarità associati.
C) Utilizzo del scala.Enumeration
modello (ispirato a questa risposta 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]
}
I seguenti elementi dalla definizione dell'enumerazione non sono disponibili (sembra essere identico all'elenco per l'utilizzo diretto di Java Enum):
Ancora una volta per i miei progetti attuali, l'articolo 7 è fondamentale per consentirmi di rilevare i problemi di compilazione se / quando aggiungo / rimuovo i membri dell'enumerazione o sto scrivendo un nuovo codice per gestire i membri dell'enumerazione esistenti.
Pertanto, data la definizione di enumerazione sopra riportata, nessuna delle tre soluzioni precedenti funziona poiché non forniscono tutto quanto indicato nella definizione di enumerazione sopra:
Ognuna di queste soluzioni può essere eventualmente rielaborata / ampliata / refactored per tentare di coprire alcuni dei requisiti mancanti di ognuno. Tuttavia, né Java Enum
né le scala.Enumeration
soluzioni possono essere sufficientemente espanse per fornire il punto 7. E per i miei progetti, questo è uno dei valori più convincenti dell'uso di un tipo chiuso all'interno di Scala. Preferisco fortemente avvisi / errori in fase di compilazione per indicare che ho un gap / problema nel mio codice piuttosto che doverlo eliminare da un'eccezione / errore di runtime di produzione.
A tale proposito, ho iniziato a lavorare con il case object
percorso per vedere se potevo produrre una soluzione che coprisse tutta la definizione di enumerazione sopra. La prima sfida è stata quella di superare il nucleo del problema di inizializzazione della classe / oggetto JVM (trattato in dettaglio in questo post StackOverflow ). E sono stato finalmente in grado di trovare una soluzione.
Poiché la mia soluzione è di due tratti; Enumerazione ed enumerazioneDecorated , e poiché il Enumeration
tratto è lungo +400 righe (molti commenti che spiegano il contesto), sto rinunciando a incollarlo in questo thread (che lo farebbe allungare considerevolmente lungo la pagina). Per i dettagli, vai direttamente al Gist .
Ecco come appare la soluzione utilizzando la stessa idea di dati di cui sopra (versione completamente commentata disponibile qui ) e implementata in 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
}
Questo è un esempio di utilizzo di una nuova coppia di tratti di enumerazione che ho creato (situato in questo Gist ) per implementare tutte le capacità desiderate e delineate nella definizione di enumerazione.
Una preoccupazione espressa è che i nomi dei membri dell'enumerazione devono essere ripetuti ( decorationOrderedSet
nell'esempio sopra). Mentre l'ho minimizzato fino a una singola ripetizione, non riuscivo a vedere come renderlo ancora meno a causa di due problemi:
getClass.getDeclaredClasses
ha un ordine indefinito (ed è abbastanza improbabile che si trovi nello stesso ordine delle case object
dichiarazioni nel codice sorgente)Alla luce di questi due problemi, ho dovuto rinunciare a cercare di generare un ordine implicito e ho dovuto richiedere esplicitamente al cliente di definirlo e dichiararlo con una sorta di nozione ordinata. Dato che le raccolte Scala non hanno un'implementazione dell'insieme ordinato inserto, la cosa migliore che potevo fare era usare un List
e quindi verificare il runtime che fosse veramente un insieme. Non è come avrei preferito averlo raggiunto.
E dato il design richiesto questo secondo elenco / set ordinazione val
, dato l' ChessPiecesEnhancedDecorated
esempio di cui sopra, è stato possibile aggiungere case object PAWN2 extends Member
e poi dimenticare di aggiungere Decoration(PAWN2,'P2', 2)
a decorationOrderedSet
. Quindi, c'è un controllo di runtime per verificare che l'elenco non sia solo un set, ma contenga TUTTI gli oggetti case che estendono il sealed trait Member
. Era una forma speciale di riflessione / macro inferno da elaborare.
Si prega di lasciare commenti e / o feedback sul Gist .
org.scalaolio.util.Enumeration
e org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Gli oggetti Case restituiscono già il loro nome per i loro metodi toString, quindi non è necessario passarlo separatamente. Ecco una versione simile a quella di jho (metodi di convenienza omessi per brevità):
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)
}
Gli oggetti sono pigri; usando vals invece possiamo eliminare la lista ma dobbiamo ripetere il nome:
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") {}
}
Se non ti dispiace un po 'di imbrogli, puoi precaricare i valori di enumerazione usando l'API reflection o qualcosa come Google Reflections. Gli oggetti case non pigri offrono la sintassi più pulita:
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
}
Bello e pulito, con tutti i vantaggi di classi di casi ed enumerazioni Java. Personalmente, definisco i valori di enumerazione all'esterno dell'oggetto per abbinare meglio il codice Scala idiomatico:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, ottengo solo valori indietro a cui ho avuto accesso in precedenza. C'è un modo per aggirare questo?
I vantaggi dell'utilizzo delle classi di casi rispetto alle enumerazioni sono:
I vantaggi dell'utilizzo delle enumerazioni anziché delle classi di casi sono:
Quindi, in generale, se hai solo bisogno di un elenco di costanti semplici per nome, usa le enumerazioni. Altrimenti, se hai bisogno di qualcosa di un po 'più complesso o desideri che la sicurezza aggiuntiva del compilatore ti dica se sono state specificate tutte le corrispondenze, usa le classi case.
AGGIORNAMENTO: Il codice seguente ha un bug, descritto qui . Il seguente programma di test funziona, ma se dovessi usare DayOfWeek.Mon (ad esempio) prima di DayOfWeek stesso, fallirebbe perché DayOfWeek non è stato inizializzato (l'uso di un oggetto interno non provoca l'inizializzazione di un oggetto esterno). Puoi ancora usare questo codice se fai qualcosa come val enums = Seq( DayOfWeek )
nella tua classe principale, forzando l'inizializzazione dei tuoi enum o puoi usare le modifiche di chaotic3quilibrium. In attesa di un enum basato su macro!
Se vuoi
quindi potrebbe essere interessante il seguente. Feedback di benvenuto.
In questa implementazione ci sono classi base Enum ed EnumVal astratte, che estendi. Vedremo quelle lezioni tra un minuto, ma prima, ecco come definiresti un enum:
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()
}
Si noti che è necessario utilizzare ciascun valore enum (chiamare il suo metodo apply) per dargli vita. [Vorrei che gli oggetti interiori non fossero pigri a meno che non glielo chiedessi espressamente. Penso.]
Naturalmente potremmo aggiungere metodi / dati a DayOfWeek, Val o ai singoli oggetti caso se lo desiderassimo.
Ed ecco come useresti un tale enum:
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"
}
}
Ecco cosa ottieni quando lo compili:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Puoi sostituire la "corrispondenza del giorno" con la corrispondenza "(giorno: @unchecked)" dove non desideri tali avvisi o semplicemente includere un caso generale alla fine.
Quando si esegue il programma precedente, si ottiene questo output:
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
Si noti che poiché l'elenco e le mappe sono immutabili, è possibile rimuovere facilmente gli elementi per creare sottoinsiemi, senza interrompere l'enum stesso.
Ecco la stessa classe Enum (e EnumVal al suo interno):
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 )
}
}
}
Ed ecco un suo uso più avanzato che controlla gli ID e aggiunge dati / metodi all'astrazione di Val e all'enum stesso:
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
] è un peccato mortale limite nel mondo del PQ": non credo che l'opinione sia universalmente accettata.
Ho una bella lib semplice qui che ti permette di usare tratti / classi sigillati come valori enum senza dover mantenere il tuo elenco di valori. Si basa su una semplice macro che non dipende dal buggy knownDirectSubclasses
.
Aggiornamento marzo 2017: come commentato da Anthony Accioly , il scala.Enumeration/enum
PR è stato chiuso.
Dotty (compilatore di prossima generazione per Scala) prenderà il comando, anche se il numero dotty del 1970 e il PR 1958 di Martin Odersky .
Nota: ora c'è (agosto 2016, 6+ anni dopo) una proposta da rimuovere scala.Enumeration
: PR 5352
Elimina
scala.Enumeration
, aggiungi@enum
annotazioneLa sintassi
@enum
class Toggle {
ON
OFF
}
è un possibile esempio di implementazione, l'intenzione è di supportare anche ADT conformi a determinate restrizioni (nessun annidamento, ricorsione o parametri variabili del costruttore), ad esempio:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Depreca il disastro non mitigato che è
scala.Enumeration
.Vantaggi di @enum rispetto a scala.Enumeration:
- Funziona davvero
- Interoperabilità Java
- Nessun problema di cancellazione
- Nessun mini-DSL confuso da imparare quando si definiscono le enumerazioni
Svantaggi: nessuno.
Questo risolve il problema di non poter avere un codebase che supporti Scala-JVM
Scala.js
e Scala-Native (codice sorgente Java non supportato suScala.js/Scala-Native
, codice sorgente Scala non in grado di definire enumerazioni accettate dalle API esistenti su Scala-JVM).
Un altro svantaggio delle classi di casi rispetto alle enumerazioni quando è necessario iterare o filtrare in tutte le istanze. Questa è una funzionalità integrata di enumerazione (e anche di enumerazioni Java) mentre le classi di casi non supportano automaticamente tale funzionalità.
In altre parole: "non esiste un modo semplice per ottenere un elenco dell'insieme totale di valori enumerati con le classi case".
Se sei seriamente intenzionato a mantenere l'interoperabilità con altri linguaggi JVM (es. Java), l'opzione migliore è scrivere enum Java. Funzionano in modo trasparente sia dal codice Scala che Java, che è più di quanto si possa dire per scala.Enumeration
o case case. Non abbiamo una nuova libreria di enumerazioni per ogni nuovo progetto di hobby su GitHub, se può essere evitato!
Ho visto varie versioni del fare in modo che una classe di casi imiti un'enumerazione. Ecco la mia versione:
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)
}
}
Ciò consente di creare classi di casi simili alle seguenti:
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)
}
Forse qualcuno potrebbe escogitare un trucco migliore che semplicemente aggiungere una classe di ciascun caso all'elenco come ho fatto io. Questo era tutto ciò che potevo inventare in quel momento.
Sono andato avanti e indietro su queste due opzioni le ultime volte che ne ho avuto bisogno. Fino a poco tempo fa, la mia preferenza era per l'opzione trait / case object sigillata.
1) Dichiarazione di enumerazione Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Tratti sigillati + oggetti caso
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Mentre nessuno di questi soddisfa davvero tutto ciò che ti dà un elenco di Java, di seguito sono i pro e i contro:
Enumerazione Scala
Pro: -Funzioni per istanziare con opzione o assumere direttamente accurate (più facile quando si carica da un archivio persistente) -L'interazione su tutti i valori possibili è supportata
Contro: -L'avviso di compilazione per la ricerca non esaustiva non è supportato (rende la corrispondenza dei modelli meno ideale)
Oggetti caso / tratti sigillati
Pro: -Utilizzando tratti sigillati, possiamo pre-istanziare alcuni valori mentre altri possono essere iniettati al momento della creazione, supporto completo per la corrispondenza dei modelli (definiti metodi di applicazione / non applicazione)
Contro: -Istantanea da un negozio persistente - spesso devi usare la corrispondenza dei modelli qui o definire il tuo elenco di tutti i possibili "valori di enum"
Ciò che alla fine mi ha fatto cambiare la mia opinione è stato qualcosa come il seguente frammento:
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
}
Le .get
chiamate erano orribili - usando l'enumerazione invece posso semplicemente chiamare il metodo withName sull'enumerazione come segue:
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)
}
}
Quindi penso che la mia preferenza in futuro sia quella di usare le enumerazioni quando si intende accedere ai valori da un repository e oggetti case / tratti sigillati altrimenti.
Preferisco case objects
(è una questione di preferenze personali). Per far fronte ai problemi inerenti a quell'approccio (analizzare la stringa e iterare su tutti gli elementi), ho aggiunto alcune righe che non sono perfette, ma sono efficaci.
Ti sto incollando il codice qui aspettandomi che possa essere utile e anche che altri possano migliorarlo.
/**
* 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 }
}
Per coloro che stanno ancora cercando come far funzionare la risposta di GatesDa : puoi semplicemente fare riferimento all'oggetto case dopo averlo dichiarato per istanziarlo:
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
}
Penso che il più grande vantaggio di avere case classes
oltre enumerations
sia che puoi usare il modello di classe di tipo aka polimorfismo ad hoc . Non è necessario abbinare enum come:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
invece avrai qualcosa del tipo:
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() = ...
}