Existe-t-il des directives de bonnes pratiques sur le moment d'utiliser des classes de cas (ou des objets de cas) par rapport à l'extension de l'énumération dans Scala?
Ils semblent offrir certains des mêmes avantages.
enum
(pour mi 2020).
Existe-t-il des directives de bonnes pratiques sur le moment d'utiliser des classes de cas (ou des objets de cas) par rapport à l'extension de l'énumération dans Scala?
Ils semblent offrir certains des mêmes avantages.
enum
(pour mi 2020).
Réponses:
Une grande différence est que les Enumeration
s viennent avec un support pour les instancier à partir d'une name
chaîne. Par exemple:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Ensuite, vous pouvez faire:
val ccy = Currency.withName("EUR")
Cela est utile lorsque vous souhaitez conserver des énumérations (par exemple, dans une base de données) ou les créer à partir de données résidant dans des fichiers. Cependant, je trouve en général que les énumérations sont un peu maladroites dans Scala et ont la sensation d'un add-on maladroit, donc j'ai maintenant tendance à utiliser l' case object
art. A case object
est plus flexible qu'une énumération:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Alors maintenant, j'ai l'avantage de ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Comme l'a souligné @ chaotic3quilibrium (avec quelques corrections pour faciliter la lecture):
En ce qui concerne le modèle "UnknownCurrency (code)", il existe d'autres moyens de ne pas trouver une chaîne de code de devise que de "casser" la nature de l'ensemble fermé du
Currency
type.UnknownCurrency
être de typeCurrency
peut désormais se faufiler dans d'autres parties d'une API.Il est conseillé de pousser ce cas à l'extérieur
Enumeration
et de faire en sorte que le client traite unOption[Currency]
type qui indiquerait clairement qu'il y a vraiment un problème de correspondance et "encourage" l'utilisateur de l'API à le résoudre lui-même.
Pour poursuivre sur les autres réponses ici, les principaux inconvénients de case object
s sur Enumeration
s sont:
Impossible d'itérer sur toutes les instances de "l'énumération" . C'est certainement le cas, mais j'ai trouvé extrêmement rare dans la pratique que cela soit nécessaire.
Impossible d'instancier facilement à partir d'une valeur persistante . Cela est également vrai, mais, sauf dans le cas d'énumérations énormes (par exemple, toutes les devises), cela ne présente pas d'énormes frais généraux.
trade.ccy
dans l'exemple de trait scellé.
case
object
génère pas une empreinte de code plus grande (~ 4x) que Enumeration
? Distinction utile en particulier pour les scala.js
projets nécessitant une faible empreinte.
MISE À JOUR: Une nouvelle solution basée sur des macros a été créée, bien supérieure à la solution que je décris ci-dessous. Je recommande fortement d'utiliser cette nouvelle solution basée sur des macros . Et il semble que Dotty fera de ce style de solution d'énumération une partie du langage. Whoo Hoo!
Résumé:
Il existe trois modèles de base pour tenter de reproduire le Java Enum
dans un projet Scala. Deux des trois modèles; en utilisant directement Java Enum
et scala.Enumeration
, ne sont pas capables d'activer la correspondance de motifs exhaustive de Scala. Et le troisième; "trait scellé + objet cas", fait ... mais a des complications d'initialisation de classe / objet JVM entraînant une génération d'index ordinal incohérente.
J'ai créé une solution avec deux classes; Enumeration and EnumerationDecorated , situé dans ce Gist . Je n'ai pas posté le code dans ce fil car le fichier pour l'énumération était assez volumineux (+400 lignes - contient beaucoup de commentaires expliquant le contexte d'implémentation).
Détails:
La question que vous posez est assez générale; "... quand utiliser les case
classesobjects
vs étendre [scala.]Enumeration
". Et il s'avère qu'il y a BEAUCOUP de réponses possibles, chaque réponse dépendant des subtilités des exigences spécifiques du projet que vous avez. La réponse peut être réduite à trois modèles de base.
Pour commencer, assurons-nous que nous travaillons à partir de la même idée de base de ce qu'est une énumération. Définissons une énumération principalement en termes de Enum
fourni à partir de Java 5 (1.5) :
Enum
, il serait bien de pouvoir utiliser explicitement le modèle de Scala correspondant à l'exhaustivité en vérifiant une énumération Ensuite, regardons les versions résumées des trois modèles de solution les plus courants publiés:
A) En fait, en utilisant directement le modèle JavaEnum
(dans un projet mixte 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;
}
}
Les éléments suivants de la définition d'énumération ne sont pas disponibles:
Pour mes projets en cours, je n'ai pas l'avantage de prendre des risques autour du parcours mixte Scala / Java. Et même si je pouvais choisir de faire un projet mixte, l'élément 7 est essentiel pour me permettre d'attraper les problèmes de temps de compilation si / quand j'ajoute / supprime des membres d'énumération, ou j'écris du nouveau code pour traiter les membres d'énumération existants.
B) En utilisant le modèle " 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}
}
Les éléments suivants de la définition d'énumération ne sont pas disponibles:
On peut soutenir qu'il répond vraiment aux éléments de définition de l'énumération 5 et 6. Pour 5, c'est un tronçon de prétendre qu'il est efficace. Pour 6, il n'est pas vraiment facile d'étendre pour contenir des données de singleton associées supplémentaires.
C) En utilisant le scala.Enumeration
modèle (inspiré de cette réponse 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]
}
Les éléments suivants de la définition d'énumération ne sont pas disponibles (il se trouve qu'ils sont identiques à la liste pour l'utilisation directe de l'énumération Java):
Encore une fois pour mes projets en cours, l'élément 7 est essentiel pour me permettre d'attraper des problèmes de temps de compilation si / quand j'ajoute / supprime des membres d'énumération, ou si j'écris du nouveau code pour traiter les membres d'énumération existants.
Donc, étant donné la définition ci-dessus d'une énumération, aucune des trois solutions ci-dessus ne fonctionne car elles ne fournissent pas tout ce qui est décrit dans la définition d'énumération ci-dessus:
Chacune de ces solutions peut éventuellement être retravaillée / étendue / refactorisée pour tenter de couvrir certaines des exigences manquantes de chacun. Cependant, ni Java Enum
ni les scala.Enumeration
solutions ne peuvent être suffisamment développées pour fournir l'élément 7. Et pour mes propres projets, c'est l'une des valeurs les plus convaincantes de l'utilisation d'un type fermé dans Scala. Je préfère fortement les erreurs / avertissements de temps de compilation pour indiquer que j'ai un écart / problème dans mon code plutôt que d'avoir à le glaner d'une exception / échec d'exécution de production.
À cet égard, je me suis mis à travailler avec la case object
voie pour voir si je pouvais produire une solution qui couvrait toute la définition d'énumération ci-dessus. Le premier défi a été de passer à travers le cœur du problème d'initialisation de classe / objet JVM (traité en détail dans cet article StackOverflow ). Et j'ai finalement pu trouver une solution.
Comme ma solution est deux traits; Enumeration et EnumerationDecorated , et puisque le Enumeration
trait fait plus de +400 lignes (beaucoup de commentaires expliquant le contexte), je renonce à le coller dans ce fil (ce qui le ferait considérablement descendre la page). Pour plus de détails, veuillez passer directement à l' essentiel .
Voici à quoi ressemble la solution en utilisant la même idée de données que ci-dessus (version entièrement commentée disponible ici ) et implémentée dans 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
}
Ceci est un exemple d'utilisation d'une nouvelle paire de traits d'énumération que j'ai créée (située dans cet Gist ) pour implémenter toutes les capacités souhaitées et décrites dans la définition d'énumération.
Une préoccupation exprimée est que les noms des membres de l'énumération doivent être répétés ( decorationOrderedSet
dans l'exemple ci-dessus). Bien que je l'ai réduit à une seule répétition, je ne voyais pas comment le rendre encore moins en raison de deux problèmes:
getClass.getDeclaredClasses
a un ordre non défini (et il est peu probable qu'il soit dans le même ordre que les case object
déclarations dans le code source)Compte tenu de ces deux problèmes, j'ai dû renoncer à générer un ordre implicite et j'ai dû explicitement demander au client de le définir et de le déclarer avec une sorte de notion d'ensemble ordonné. Comme les collections Scala n'ont pas d'implémentation d'ensemble d'insertions ordonnées, le mieux que je pouvais faire était d'utiliser un List
et puis de vérifier à l'exécution qu'il s'agissait vraiment d'un ensemble. Ce n'est pas comme ça que j'aurais préféré y arriver.
Et étant donné la conception nécessaire cette deuxième liste / commande ensemble val
, compte tenu de l' ChessPiecesEnhancedDecorated
exemple ci - dessus, il est possible d'ajouter case object PAWN2 extends Member
, puis oublier d'ajouter Decoration(PAWN2,'P2', 2)
à decorationOrderedSet
. Ainsi, il y a un contrôle d'exécution pour vérifier que la liste n'est pas seulement un ensemble, mais contient TOUS les objets de cas qui étendent le sealed trait Member
. C'était une forme spéciale de réflexion / macro enfer à travailler.
Veuillez laisser des commentaires et / ou des commentaires sur le Gist .
org.scalaolio.util.Enumeration
et org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Les objets Case renvoient déjà leur nom pour leurs méthodes toString, il n'est donc pas nécessaire de les transmettre séparément. Voici une version similaire à celle de jho (méthodes de commodité omises par souci de concision):
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)
}
Les objets sont paresseux; en utilisant vals à la place, nous pouvons supprimer la liste, mais nous devons répéter le nom:
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") {}
}
Si cela ne vous dérange pas, vous pouvez précharger vos valeurs d'énumération en utilisant l'API de réflexion ou quelque chose comme Google Reflections. Les objets cas non paresseux vous offrent la syntaxe la plus nette:
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
}
Agréable et propre, avec tous les avantages des classes de cas et des énumérations Java. Personnellement, je définis les valeurs d'énumération en dehors de l'objet pour mieux correspondre au code Scala idiomatique:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, je ne récupère que les valeurs auxquelles j'ai déjà accédé. Y a-t-il un moyen de contourner cela?
Les avantages de l'utilisation des classes de cas par rapport aux énumérations sont les suivants:
Les avantages de l'utilisation des énumérations au lieu des classes de cas sont les suivants:
Donc, en général, si vous avez juste besoin d'une liste de constantes simples par nom, utilisez des énumérations. Sinon, si vous avez besoin de quelque chose d'un peu plus complexe ou si vous voulez la sécurité supplémentaire du compilateur vous indiquant si toutes les correspondances sont spécifiées, utilisez des classes de cas.
MISE À JOUR: Le code ci-dessous a un bug, décrit ici . Le programme de test ci-dessous fonctionne, mais si vous deviez utiliser DayOfWeek.Mon (par exemple) avant DayOfWeek lui-même, il échouerait car DayOfWeek n'a pas été initialisé (l'utilisation d'un objet interne ne provoque pas l'initialisation d'un objet externe). Vous pouvez toujours utiliser ce code si vous faites quelque chose comme val enums = Seq( DayOfWeek )
dans votre classe principale, forçant l'initialisation de vos énumérations, ou vous pouvez utiliser les modifications de chaotic3quilibrium. Dans l'attente d'une énumération basée sur les macros!
Si tu veux
alors ce qui suit peut être intéressant. Commentaires bienvenus.
Dans cette implémentation, il existe des classes de base Enum et EnumVal abstraites que vous étendez. Nous verrons ces classes dans une minute, mais d'abord, voici comment vous définiriez une énumération:
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()
}
Notez que vous devez utiliser chaque valeur d'énumération (appelez sa méthode apply) pour lui donner vie. [Je souhaite que les objets intérieurs ne soient pas paresseux à moins que je ne le demande spécifiquement. Je pense.]
Nous pouvons bien sûr ajouter des méthodes / données à DayOfWeek, Val ou aux objets de cas individuels si nous le souhaitons.
Et voici comment vous utiliseriez une telle énumération:
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"
}
}
Voici ce que vous obtenez lorsque vous le compilez:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Vous pouvez remplacer "match du jour" par "(jour: @unchecked) match" où vous ne voulez pas de tels avertissements, ou simplement inclure un cas fourre-tout à la fin.
Lorsque vous exécutez le programme ci-dessus, vous obtenez cette sortie:
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
Notez que puisque la liste et les cartes sont immuables, vous pouvez facilement supprimer des éléments pour créer des sous-ensembles, sans casser l'énumération elle-même.
Voici la classe Enum elle-même (et EnumVal en son sein):
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 )
}
}
}
Et voici une utilisation plus avancée de celui-ci qui contrôle les ID et ajoute des données / méthodes à l'abstraction Val et à l'énumération elle-même:
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
] est un péché mortel limite dans le monde de la PF" - je ne pense pas que l'opinion soit universellement acceptée.
J'ai une belle bibliothèque simple ici qui vous permet d'utiliser des traits / classes scellés comme valeurs d'énumération sans avoir à maintenir votre propre liste de valeurs. Il s'appuie sur une simple macro qui ne dépend pas du buggyknownDirectSubclasses
.
Mise à jour mars 2017: comme l'a commenté Anthony Accioly , le scala.Enumeration/enum
PR a été fermé.
Dotty (compilateur de nouvelle génération pour Scala) prendra la tête, bien que le numéro de dotty 1970 et le PR 1958 de Martin Odersky .
Remarque: il y a maintenant (août 2016, 6 ans et plus plus tard) une proposition de suppression scala.Enumeration
: PR 5352
Déprécier
scala.Enumeration
, ajouter@enum
annotationLa syntaxe
@enum
class Toggle {
ON
OFF
}
est un exemple d'implémentation possible, l'intention est également de prendre en charge les ADT conformes à certaines restrictions (pas d'imbrication, de récursivité ou de paramètres de constructeur variables), par exemple:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Déprécie la catastrophe non atténuée qui est
scala.Enumeration
.Avantages de @enum par rapport à scala.
- Fonctionne réellement
- Java interop
- Aucun problème d'effacement
- Pas de mini-DSL déroutant à apprendre lors de la définition des énumérations
Inconvénients: aucun.
Cela résout le problème de ne pas pouvoir avoir une base de code prenant en charge Scala-JVM
Scala.js
et Scala-Native (code source Java non pris en charge surScala.js/Scala-Native
, code source Scala ne pouvant pas définir d'énumérations acceptées par les API existantes sur Scala-JVM).
Un autre inconvénient des classes de cas par rapport aux énumérations lorsque vous devrez itérer ou filtrer sur toutes les instances. Il s'agit d'une capacité intégrée d'énumération (et d'énumérations Java également) tandis que les classes de cas ne prennent pas automatiquement en charge une telle capacité.
En d'autres termes: "il n'y a pas de moyen facile d'obtenir une liste de l'ensemble total des valeurs énumérées avec les classes de cas".
Si vous souhaitez sérieusement maintenir l'interopérabilité avec d'autres langages JVM (par exemple Java), la meilleure option est d'écrire des énumérations Java. Ceux-ci fonctionnent de manière transparente à partir du code Scala et Java, ce qui est plus que ce qui peut être dit pour les scala.Enumeration
objets ou les cas. N'ayons pas une nouvelle bibliothèque d'énumérations pour chaque nouveau projet de passe-temps sur GitHub, si cela peut être évité!
J'ai vu différentes versions de faire une classe de cas imiter une énumération. Voici ma version:
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)
}
}
Ce qui vous permet de construire des classes de cas qui ressemblent à ceci:
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)
}
Peut-être que quelqu'un pourrait trouver une meilleure astuce que d'ajouter simplement une classe de cas à la liste comme je l'ai fait. C'est tout ce que j'ai pu trouver à l'époque.
J'ai fait des allers-retours sur ces deux options les dernières fois où j'en ai eu besoin. Jusqu'à récemment, ma préférence a été pour l'option d'objet trait / cas scellé.
1) Déclaration de dénombrement Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Traits scellés + objets de caisse
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Bien qu'aucun de ces éléments ne réponde vraiment à tout ce qu'une énumération java vous offre, voici les avantages et les inconvénients:
Énumération Scala
Avantages: -Fonctions pour instancier avec option ou supposant directement précis (plus facile lors du chargement à partir d'un magasin persistant) -Itération sur toutes les valeurs possibles est prise en charge
Inconvénients: l'avertissement de compilation pour la recherche non exhaustive n'est pas pris en charge (rend la correspondance des modèles moins idéale)
Objets / traits scellés
Avantages: -En utilisant des traits scellés, nous pouvons pré-instancier certaines valeurs tandis que d'autres peuvent être injectées au moment de la création
Inconvénients: -Instanciation à partir d'un magasin persistant - vous devez souvent utiliser la correspondance de modèles ici ou définir votre propre liste de toutes les "valeurs d'énumération" possibles
Ce qui m'a finalement fait changer d'avis était quelque chose comme l'extrait suivant:
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
}
Les .get
appels étaient hideux - en utilisant l'énumération à la place, je peux simplement appeler la méthode withName sur l'énumération comme suit:
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)
}
}
Je pense donc que ma préférence pour l'avenir est d'utiliser les énumérations lorsque les valeurs sont destinées à être accessibles à partir d'un référentiel et les objets / traits scellés dans le cas contraire.
je préfère case objects
(c'est une question de préférence personnelle). Pour faire face aux problèmes inhérents à cette approche (analyser la chaîne et itérer sur tous les éléments), j'ai ajouté quelques lignes qui ne sont pas parfaites, mais qui sont efficaces.
Je vous colle le code ici en espérant qu'il pourrait être utile, et que d'autres pourraient l'améliorer.
/**
* 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 }
}
Pour ceux qui cherchent encore à faire fonctionner la réponse de GatesDa : Vous pouvez simplement référencer l'objet case après l'avoir déclaré pour l'instancier:
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
}
Je pense que le plus grand avantage d'avoir case classes
plus enumerations
est que vous pouvez utiliser le modèle de classe de type aka polymorphysme ad hoc . Pas besoin de faire correspondre des énumérations comme:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
à la place, vous aurez quelque chose comme:
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() = ...
}