242 votes

Cas des classes vs énumérations en Scala

Existe-t-il des lignes directrices de bonnes pratiques sur quand utiliser les classes de cas vs s’étendant de l’énumération Scala ?

Ils semblent offrir certains des mêmes avantages.

230voto

oxbow_lakes Points 70013

Une grande différence est qu' Enumerations viennent avec le soutien de l'instanciation de certains name Chaîne de caractères. Par exemple:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

Ensuite, vous pouvez faire:

val ccy = Currency.withName("EUR")

Ceci est utile lorsque l'on souhaite faire persister les énumérations (par exemple, pour une base de données) ou les créer à partir de données contenues dans des fichiers résidant. Cependant, je trouve en général que les énumérations sont un peu maladroit dans Scala et avoir la sensation d'un maladroit add-on, donc j'ai maintenant tendance à utiliser case objects. Un case object est plus souple qu'un enum:

sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.

case class UnknownCurrency(name: String) extends Currency

Maintenant, j'ai l'avantage de...

trade.ccy match {
  case EUR                   =>
  case UnknownCurrency(code) =>
}

Pour continuer à suivre les autres réponses ici, les principaux inconvénients de l' case objects sur Enumerations sont:

  1. Ne peut pas effectuer une itération sur toutes les instances de la "énumération". C'est certainement le cas, mais j'ai trouvé ça extrêmement rare en pratique qu'il est nécessaire.

  2. Ne peut pas instancier facilement à partir persisté valeur. C'est également vrai, mais, sauf dans le cas de grand énumérations (par exemple, toutes les devises), ce n'est pas de présenter une surcharge énorme.

62voto

GatesDA Points 490

Cas des objets déjà le retour de leur nom pour leur toString méthodes, afin de la passer sous séparément, il est inutile. Voici une version similaire à jho (méthodes pratiques omis par souci de clarté):

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; à l'aide de vals-les-bains au lieu de cela, nous pouvons supprimer la liste, mais avoir à répéter le nom de:

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 vous n'avez pas l'esprit de la tricherie certains, vous pouvez pré-charger votre énumération de valeurs à l'aide de l'API reflection ou quelque chose comme Google Réflexions. Non paresseux cas des objets de vous donner le plus propre syntaxe:

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 de classes Java et les énumérations. Personnellement, j'définir les valeurs d'énumération à l'extérieur de l'objet, pour mieux correspondre à idiomatiques Scala code:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency

29voto

Aaron Points 1744

Les avantages de l'utilisation des classes de cas sur les Énumérations sont:

  • Lors de l'utilisation de scellés cas des classes, de la Scala compilateur ne peut pas savoir si le match est complètement spécifié, par exemple, lorsque toutes les correspondances possibles sont adoptés dans la correspondance de déclaration. Avec les énumérations, de la Scala compilateur ne peut pas dire.
  • Cas des classes de soutient naturellement plus de champs qu'une Valeur de l'Énumération qui prend en charge un nom et un ID.

Les avantages de l'utilisation des Énumérations à la place de classes de cas sont les suivants:

  • Les énumérations sera généralement un peu moins de code à écrire.
  • Les énumérations sont un peu plus facile à comprendre pour quelqu'un de nouveau à la Scala, car ils sont répandus dans d'autres langues

Donc, en général, si vous avez besoin d'une liste de constantes simple par nom, utiliser les énumérations. Sinon, si vous avez besoin de quelque chose d'un peu plus complexes ou qui veulent le plus de sécurité, le compilateur vous dire si vous avez tous les matches spécifié, cas d'utilisation des classes.

15voto

AmigoNico Points 2117

Mise à JOUR: Le code ci-dessous a un bug, décrit ici. Le programme de test ci-dessous fonctionne, mais si vous étiez à utiliser DayOfWeek.Mon (par exemple) avant de DayOfWeek lui-même, il ne pourrait pas parce que DayOfWeek n'a pas été initialisé (utilisation intérieure d'un objet ne cause pas un objet extérieur à être initialisé). Vous pouvez toujours utiliser ce code si vous faites quelque chose comme val enums = Seq( DayOfWeek ) dans votre classe principale, forcer l'initialisation de votre énumérations, ou vous pouvez utiliser chaotic3quilibrium modifications. Impatient à une macro-fonction enum!


Si vous voulez

  • mises en garde au sujet de la non-exhaustive modèle correspond
  • un Int ID affecté à chaque valeur d'enum, que vous pouvez éventuellement contrôle
  • une Liste immuable des valeurs enum, dans l'ordre qu'ils ont été définis
  • immuable la Carte, d'un nom à valeur d'enum
  • est immuable de Carte d'identité à la valeur d'enum
  • lieux de bâton de méthodes et de données pour tous les particuliers ou les valeurs de l'enum, ou pour les enum dans son ensemble
  • ordonnée les valeurs de l'enum (de sorte que vous pouvez tester, par exemple, que ce soit le jour < mercredi)
  • la possibilité de prolonger un enum pour en créer d'autres

les mesures suivantes peuvent être d'intérêt. Commentaires de bienvenue.

Dans cette application il y a abstrait Enum et EnumVal les classes de base, qui vous étendre. Nous allons voir ces classes dans une minute, mais d'abord, voici comment vous définiriez 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()
}

Notez que vous devez utiliser chaque valeur d'enum (appeler sa méthode apply) pour l'amener à la vie. [Je tiens intérieure d'objets n'étaient pas paresseux à moins de me demander spécifiquement pour eux. Je pense.]

On pourrait bien sûr ajouter des méthodes/données à DayOfWeek, Val, ou le cas particulier des objets si on le désire.

Et voici comment vous pourriez utiliser un 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"
  }

}

Voici ce que vous obtenez lorsque vous 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 "jour de match" avec "( jour: @décochée ) match" où vous ne voulez pas de tels avertissements, ou simplement inclure un fourre-tout à la fin.

Lorsque vous exécutez le programme ci-dessus, vous obtenez ce résultat:

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

À noter que depuis la Liste et les Cartes sont immuables, vous pouvez facilement supprimer des éléments pour créer des sous-ensembles, sans casser la enum lui-même.

Voici l'énumération de la classe elle-même (et EnumVal à l'intérieur):

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 poussée de ce qui contrôle l'Id et ajoute des données/méthodes du Val de l'abstraction et de l'enum lui-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)
}

8voto

user142435 Points 331

Un autre inconvénient des classes affaire contre énumérations lorsque vous devez effectuer une itération ou filtrer dans toutes les instances. Il s’agit d’une fonctionnalité d’énumération (et les énumérations Java aussi bien) alors que les classes affaire automatiquement ne supportent pas cette fonctionnalité.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X