¿Hay alguna guía de mejores prácticas sobre cuándo usar las clases de casos? (u objetos de casos) en lugar de extender la enumeración en Scala?
Parecen ofrecer algunos de los mismos beneficios.
enum
(para mediados de 2020).
¿Hay alguna guía de mejores prácticas sobre cuándo usar las clases de casos? (u objetos de casos) en lugar de extender la enumeración en Scala?
Parecen ofrecer algunos de los mismos beneficios.
enum
(para mediados de 2020).
Respuestas:
Una gran diferencia es que Enumeration
viene con soporte para crear instancias de algunos name
String. Por ejemplo:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Entonces puedes hacer:
val ccy = Currency.withName("EUR")
Esto es útil cuando se desea conservar las enumeraciones (por ejemplo, en una base de datos) o crearlas a partir de datos que residen en archivos. Sin embargo, encuentro en general que las enumeraciones son un poco torpes en Scala y tienen la sensación de un complemento incómodo, por lo que ahora tiendo a usar case object
s. A case object
es más flexible que una enumeración:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Entonces ahora tengo la ventaja de ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Como señaló @ chaotic3quilibrium (con algunas correcciones para facilitar la lectura):
Con respecto al patrón "UnknownCurrency (code)", hay otras formas de manejar el hecho de no encontrar una cadena de código de moneda que "romper" la naturaleza del conjunto cerrado del
Currency
tipo.UnknownCurrency
ser de tipoCurrency
ahora puede colarse en otras partes de una API.Es aconsejable sacar ese caso al exterior
Enumeration
y hacer que el cliente trate con unOption[Currency]
tipo que indique claramente que realmente hay un problema de coincidencia y "aliente" al usuario de la API a que lo resuelva por sí mismo.
Para seguir las otras respuestas aquí, los principales inconvenientes de case object
s sobre Enumeration
s son:
No se puede iterar sobre todas las instancias de la "enumeración" . Este es ciertamente el caso, pero me ha resultado extremadamente raro en la práctica que sea necesario.
No se puede crear una instancia fácilmente del valor persistente . Esto también es cierto, pero, excepto en el caso de grandes enumeraciones (por ejemplo, todas las monedas), esto no presenta una gran sobrecarga.
trade.ccy
con el ejemplo de rasgo sellado.
case
object
genera una huella de código mayor (~ 4x) que Enumeration
? Distinción útil especialmente para scala.js
proyectos que necesitan una pequeña huella.
ACTUALIZACIÓN: Se ha creado una nueva solución basada en macro que es muy superior a la solución que describo a continuación. Recomiendo usar esta nueva solución basada en macro . Y parece que los planes para Dotty harán que este estilo de solución de enumeración sea parte del lenguaje. Whoohoo!
Resumen:
Existen tres patrones básicos para intentar reproducir Java Enum
dentro de un proyecto Scala. Dos de los tres patrones; directamente utilizando Java Enum
y scala.Enumeration
, no son capaces de habilitar la exhaustiva coincidencia de patrones de Scala. Y el tercero; "rasgo sellado + objeto de caso", sí ... pero tiene complicaciones de inicialización de clase / objeto JVM que resultan en una generación de índice ordinal inconsistente.
He creado una solución con dos clases; Enumeración y Enumeración Decorado , ubicado en este Gist . No publiqué el código en este hilo ya que el archivo para Enumeration era bastante grande (+400 líneas - contiene muchos comentarios que explican el contexto de implementación).
Detalles:
la pregunta que hace es bastante general; "... cuándo usar case
clasesobjects
vs extender [scala.]Enumeration
". Y resulta que hay MUCHAS respuestas posibles, cada respuesta depende de las sutilezas de los requisitos específicos del proyecto que tenga. La respuesta se puede reducir a tres patrones básicos.
Para comenzar, asegurémonos de que trabajamos desde la misma idea básica de qué es una enumeración. Definamos una enumeración principalmente en términos de lo Enum
proporcionado a partir de Java 5 (1.5) :
Enum
, sería bueno poder explícitamente aprovechar la comprobación de exhaustividad de coincidencia de patrones de Scala para una enumeración A continuación, echemos un vistazo a las versiones resumidas de los tres patrones de solución más comunes publicados:
A) Realmente usando directamente el patrón JavaEnum
(en un proyecto mixto 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;
}
}
Los siguientes elementos de la definición de enumeración no están disponibles:
Para mis proyectos actuales, no tengo el beneficio de correr riesgos en torno a la ruta del proyecto mixto Scala / Java. E incluso si pudiera elegir hacer un proyecto mixto, el elemento 7 es crítico para permitirme detectar problemas de tiempo de compilación si / cuando agrego / elimino miembros de enumeración, o estoy escribiendo algún código nuevo para tratar con los miembros de enumeración existentes.
B) Usando el patrón " 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}
}
Los siguientes elementos de la definición de enumeración no están disponibles:
Es discutible que realmente cumpla con los elementos de definición de enumeración 5 y 6. Para 5, es difícil afirmar que es eficiente. Para 6, no es realmente fácil extenderlo para contener datos adicionales asociados de soltería.
C) Usando el scala.Enumeration
patrón (inspirado en esta respuesta de 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]
}
Los siguientes elementos de la definición de enumeración no están disponibles (resulta ser idéntico a la lista para usar directamente la enumeración de Java):
Nuevamente para mis proyectos actuales, el ítem 7 es crítico para permitirme detectar problemas de tiempo de compilación si / cuando agrego / elimino miembros de enumeración, o estoy escribiendo algún código nuevo para tratar con los miembros de enumeración existentes.
Entonces, dada la definición anterior de una enumeración, ninguna de las tres soluciones anteriores funciona ya que no proporcionan todo lo que se describe en la definición de enumeración anterior:
Cada una de estas soluciones puede eventualmente ser modificada / ampliada / refactorizada para intentar cubrir algunos de los requisitos faltantes de cada uno. Sin embargo, ni Java Enum
ni las scala.Enumeration
soluciones pueden expandirse lo suficiente como para proporcionar el ítem 7. Y para mis propios proyectos, este es uno de los valores más convincentes de usar un tipo cerrado dentro de Scala. Prefiero encarecidamente las advertencias / errores de tiempo de compilación para indicar que tengo un espacio / problema en mi código en lugar de tener que recogerlo de una excepción / falla de tiempo de ejecución de producción.
En ese sentido, me puse a trabajar con la case object
vía para ver si podía producir una solución que cubriera toda la definición de enumeración anterior. El primer desafío fue superar el núcleo del problema de inicialización de clase / objeto JVM (cubierto en detalle en esta publicación de StackOverflow ). Y finalmente pude encontrar una solución.
Como mi solución son dos rasgos; Enumeration y EnumerationDecorated , y dado que el Enumeration
rasgo tiene más de +400 líneas de largo (muchos comentarios explican el contexto), renuncio a pegarlo en este hilo (lo que haría que se extienda considerablemente por la página). Para más detalles, vaya directamente a Gist .
Así es como se ve la solución usando la misma idea de datos que la anterior (versión totalmente comentada disponible aquí ) e implementada en 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
}
Este es un ejemplo de uso de un nuevo par de rasgos de enumeración que creé (ubicado en este Gist ) para implementar todas las capacidades deseadas y descritas en la definición de enumeración.
Una preocupación expresada es que los nombres de los miembros de la enumeración deben repetirse ( decorationOrderedSet
en el ejemplo anterior). Si bien lo minimicé a una sola repetición, no pude ver cómo hacerlo aún menos debido a dos problemas:
getClass.getDeclaredClasses
tiene un orden indefinido (y es poco probable que esté en el mismo orden que las case object
declaraciones en el código fuente)Dados estos dos problemas, tuve que renunciar a intentar generar un pedido implícito y tuve que exigir explícitamente que el cliente lo defina y lo declare con algún tipo de noción de conjunto ordenado. Como las colecciones de Scala no tienen una implementación de conjunto ordenado por inserción, lo mejor que pude hacer fue usar un List
y luego verificar que realmente era un conjunto. No es como hubiera preferido haber logrado esto.
Y teniendo en cuenta el diseño requiere esta segunda lista / pedido conjunto val
, dado el ChessPiecesEnhancedDecorated
ejemplo anterior, era posible añadir case object PAWN2 extends Member
y luego olvidarse de añadir Decoration(PAWN2,'P2', 2)
a decorationOrderedSet
. Por lo tanto, hay una comprobación de tiempo de ejecución para verificar que la lista no es solo un conjunto, sino que contiene TODOS los objetos de caso que extienden el sealed trait Member
. Esa fue una forma especial de reflexión / macro infierno para trabajar.
Por favor, deje comentarios y / o comentarios sobre el Gist .
org.scalaolio.util.Enumeration
y org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Los objetos de caso ya devuelven su nombre para sus métodos toString, por lo que no es necesario pasarlo por separado. Aquí hay una versión similar a la de jho (métodos de conveniencia omitidos por brevedad):
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)
}
Los objetos son vagos; al usar vals en su lugar, podemos soltar la lista pero tenemos que repetir el nombre:
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 no le importa hacer trampa, puede precargar sus valores de enumeración usando la API de reflexión o algo así como Google Reflections. Los objetos de caso no perezosos le dan la sintaxis más limpia:
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
}
Agradable y limpio, con todas las ventajas de las clases de casos y las enumeraciones de Java. Personalmente, defino los valores de enumeración fuera del objeto para que coincidan mejor con el código idiomático de Scala:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, solo obtengo valores a los que he accedido anteriormente. ¿Hay alguna forma de evitar eso?
Las ventajas de usar clases de caso sobre Enumeraciones son:
Las ventajas de usar Enumeraciones en lugar de clases de casos son:
Entonces, en general, si solo necesita una lista de constantes simples por nombre, use enumeraciones. De lo contrario, si necesita algo un poco más complejo o desea que la seguridad adicional del compilador le diga si tiene todas las coincidencias especificadas, use las clases de casos.
ACTUALIZACIÓN: El siguiente código tiene un error, que se describe aquí . El siguiente programa de prueba funciona, pero si fuera a usar DayOfWeek.Mon (por ejemplo) antes de DayOfWeek, fallaría porque DayOfWeek no se ha inicializado (el uso de un objeto interno no hace que se inicialice un objeto externo). Todavía puede usar este código si hace algo como val enums = Seq( DayOfWeek )
en su clase principal, forzando la inicialización de sus enumeraciones, o puede usar las modificaciones de chaotic3quilibrium. ¡Esperamos una enumeración basada en macros!
Si tu quieres
entonces lo siguiente puede ser de interés. Comentarios bienvenidos.
En esta implementación hay clases base abstractas Enum y EnumVal, que usted extiende. Veremos esas clases en un minuto, pero primero, así es como definiría una enumeración:
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()
}
Tenga en cuenta que debe usar cada valor de enumeración (llame a su método de aplicación) para darle vida. [Desearía que los objetos internos no fueran perezosos a menos que pida específicamente que lo sean. Yo creo que.]
Por supuesto, podríamos agregar métodos / datos a DayOfWeek, Val o los objetos de caso individuales si así lo deseáramos.
Y así es como usarías tal enumeración:
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"
}
}
Esto es lo que obtienes cuando lo compilas:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Puede reemplazar "coincidencia de día" con "coincidencia (día: @ no comprobado)" donde no desea tales advertencias, o simplemente incluir un caso general al final.
Cuando ejecuta el programa anterior, obtiene esta salida:
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
Tenga en cuenta que, dado que la Lista y los Mapas son inmutables, puede eliminar fácilmente elementos para crear subconjuntos, sin romper la enumeración.
Aquí está la clase Enum en sí (y EnumVal dentro de ella):
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 )
}
}
}
Y aquí hay un uso más avanzado que controla los ID y agrega datos / métodos a la abstracción Val y a la enumeración misma:
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
] es un pecado mortal límite en el mundo de la PF". No creo que la opinión sea universalmente aceptada.
Tengo una buena biblioteca simple aquí que le permite usar rasgos / clases selladas como valores de enumeración sin tener que mantener su propia lista de valores. Se basa en una macro simple que no depende del buggy knownDirectSubclasses
.
Actualización de marzo de 2017: como comentó Anthony Accioly , el scala.Enumeration/enum
RP ha sido cerrado.
Dotty (compilador de la próxima generación para Scala) tomará la delantera, aunque en el número de 1970 y en el PR 1958 de Martin Odersky .
Nota: ahora hay (agosto de 2016, más de 6 años después) una propuesta para eliminar scala.Enumeration
: PR 5352
Desaprobar
scala.Enumeration
, agregar@enum
anotacionesLa sintaxis
@enum
class Toggle {
ON
OFF
}
es un posible ejemplo de implementación, la intención es también admitir ADT que cumplan con ciertas restricciones (sin anidamiento, recursión o parámetros variables del constructor), por ejemplo:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Deprecia el desastre no mitigado que es
scala.Enumeration
.Ventajas de @enum sobre scala. Enumeración:
- En realidad funciona
- Interoperabilidad Java
- No hay problemas de borrado
- No hay mini-DSL confusas para aprender al definir enumeraciones
Desventajas: ninguna.
Esto soluciona el problema de no poder tener una base de código compatible con Scala-JVM
Scala.js
y Scala-Native (el código fuente de Java no es compatibleScala.js/Scala-Native
, el código fuente de Scala no puede definir enumeraciones que son aceptadas por las API existentes en Scala-JVM).
Otra desventaja de las clases de casos versus Enumeraciones cuando necesitará iterar o filtrar en todas las instancias. Esta es una capacidad incorporada de Enumeration (y las enumeraciones de Java también), mientras que las clases de casos no admiten automáticamente dicha capacidad.
En otras palabras: "no hay una manera fácil de obtener una lista del conjunto total de valores enumerados con clases de casos".
Si realmente quiere mantener la interoperabilidad con otros lenguajes JVM (por ejemplo, Java), la mejor opción es escribir enumeraciones Java. Esos funcionan de manera transparente tanto desde el código Scala como desde Java, que es más de lo que se puede decir de los scala.Enumeration
objetos de caso. ¡No podemos tener una nueva biblioteca de enumeraciones para cada nuevo proyecto de pasatiempo en GitHub, si se puede evitar!
He visto varias versiones de hacer que una clase de caso imite una enumeración. Aquí está mi versión:
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)
}
}
Lo que le permite construir clases de casos similares a las siguientes:
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)
}
Tal vez alguien podría idear un truco mejor que simplemente agregar una clase de cada caso a la lista como lo hice yo. Esto fue todo lo que se me ocurrió en ese momento.
He estado yendo y viniendo de estas dos opciones las últimas veces que las he necesitado. Hasta hace poco, mi preferencia ha sido la opción de objeto de rasgo / caso sellado.
1) Declaración de enumeración Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Rasgos Sellados + Objetos de Caso
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Si bien ninguno de estos realmente cumple con todo lo que una enumeración de Java le brinda, a continuación se detallan los pros y los contras:
Enumeración Scala
Pros: -Funciones para crear instancias con opción o asumir directamente la precisión (más fácil cuando se carga desde una tienda persistente) -Se admite la iteración sobre todos los valores posibles
Contras: -La advertencia de compilación para la búsqueda no exhaustiva no es compatible (hace que la coincidencia de patrones sea menos ideal)
Objetos de caso / rasgos sellados
Pros: -Utilizando rasgos sellados, podemos pre-instanciar algunos valores, mientras que otros pueden inyectarse en el momento de la creación -completo soporte para la coincidencia de patrones (métodos de aplicar / no aplicar definidos)
Contras: -Instantando desde una tienda persistente - a menudo tienes que usar la coincidencia de patrones aquí o definir tu propia lista de todos los 'valores de enumeración' posibles
Lo que finalmente me hizo cambiar mi opinión fue algo como el siguiente fragmento:
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
}
Las .get
llamadas fueron horribles: en su lugar, usando la enumeración simplemente puedo llamar al método withName en la enumeración de la siguiente manera:
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)
}
}
Por lo tanto, creo que mi preferencia en el futuro es usar Enumeraciones cuando se pretende acceder a los valores desde un repositorio y objetos de caso / rasgos sellados de lo contrario.
Prefiero case objects
(es una cuestión de preferencia personal). Para hacer frente a los problemas inherentes a ese enfoque (analizar la cadena e iterar sobre todos los elementos), he agregado algunas líneas que no son perfectas, pero son efectivas.
Te estoy pegando el código aquí esperando que pueda ser útil, y también que otros puedan mejorarlo.
/**
* 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 }
}
Para aquellos que todavía buscan cómo hacer que funcione la respuesta de GatesDa : puede hacer referencia al objeto de caso después de declararlo para instanciarlo:
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
}
Creo que la mayor ventaja de tener case classes
más enumerations
es que puedes usar el patrón de clase de tipo también conocido como polimorfismo ad-hoc . No es necesario que coincida con enumeraciones como:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
en cambio tendrás algo como:
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() = ...
}