Existem diretrizes de boas práticas sobre quando usar classes de caso (ou objetos de caso) versus estender a enumeração no Scala?
Eles parecem oferecer alguns dos mesmos benefícios.
enum
(para meados de 2020).
Existem diretrizes de boas práticas sobre quando usar classes de caso (ou objetos de caso) versus estender a enumeração no Scala?
Eles parecem oferecer alguns dos mesmos benefícios.
enum
(para meados de 2020).
Respostas:
Uma grande diferença é que eles Enumeration
vêm com suporte para instancia-los a partir de alguma name
String. Por exemplo:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Então você pode fazer:
val ccy = Currency.withName("EUR")
Isso é útil quando se deseja persistir enumerações (por exemplo, em um banco de dados) ou criá-las a partir de dados residentes em arquivos. No entanto, acho que, em geral, as enumerações são um pouco desajeitadas no Scala e têm a sensação de um complemento estranho, então agora tenho a tendência de usar case object
s. A case object
é mais flexível que um enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Então agora eu tenho a vantagem de ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Como o @ chaotic3quilibrium apontou (com algumas correções para facilitar a leitura):
Em relação ao padrão "UnknownCurrency (code)", há outras maneiras de lidar com a não localização de uma sequência de códigos de moeda além de "quebrar" a natureza do conjunto fechado do
Currency
tipo.UnknownCurrency
ser do tipoCurrency
agora pode se infiltrar em outras partes de uma API.É aconselhável levar esse caso para fora
Enumeration
e fazer com que o cliente lide com umOption[Currency]
tipo que indique claramente que há realmente um problema de correspondência e "incentive" o usuário da API a resolver por conta própria.
Para acompanhar as outras respostas aqui, as principais desvantagens de case object
s sobre Enumeration
s são:
Não é possível iterar em todas as instâncias da "enumeração" . Certamente é esse o caso, mas achei extremamente raro na prática que isso seja necessário.
Não é possível instanciar facilmente a partir do valor persistente . Isso também é verdade, mas, exceto no caso de grandes enumerações (por exemplo, todas as moedas), isso não apresenta uma sobrecarga enorme.
trade.ccy
no exemplo de característica selada.
case
object
gera uma pegada de código maior (~ 4x) que Enumeration
? Distinção útil, especialmente para scala.js
projetos que necessitam de uma pegada reduzida.
ATUALIZAÇÃO: Uma nova solução baseada em macro foi criada, que é muito superior à solução descrita abaixo. Eu recomendo fortemente o uso desta nova solução baseada em macro . E parece que os planos para o Dotty farão com que esse estilo de solução enum faça parte do idioma. Whoohoo!
Resumo:
Existem três padrões básicos para tentar reproduzir o Java Enum
em um projeto Scala. Dois dos três padrões; diretamente usando Java Enum
e scala.Enumeration
, não são capazes de ativar a correspondência exaustiva de padrões do Scala. E o terceiro; "traço selado + objeto de caso", possui ... mas possui complicações de inicialização de classe / objeto da JVM, resultando em geração inconsistente de índice ordinal.
Eu criei uma solução com duas classes; Enumeração e EnumerationDecorated , localizado nesta Gist . Não publiquei o código nesse segmento, pois o arquivo para Enumeração era bastante grande (+400 linhas - contém muitos comentários explicando o contexto de implementação).
Detalhes:
a pergunta que você está fazendo é bem geral; "... quando usar case
classesobjects
versus extensão [scala.]Enumeration
". E acontece que existem MUITAS respostas possíveis, cada resposta dependendo das sutilezas dos requisitos específicos do projeto que você possui. A resposta pode ser reduzida para três padrões básicos.
Para começar, vamos ter certeza de que estamos trabalhando com a mesma idéia básica do que é uma enumeração. Vamos definir uma enumeração principalmente em termos do Enum
fornecido no Java 5 (1.5) :
Enum
, seria bom poder aproveitar explicitamente a verificação exaustiva de correspondência de padrões do Scala para uma enumeração A seguir, vejamos as versões resumidas dos três padrões de solução mais comuns publicados:
A) Na verdade, diretamente usando o padrão JavaEnum
(em um projeto Scala / Java misto):
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;
}
}
Os seguintes itens da definição de enumeração não estão disponíveis:
Para meus projetos atuais, não tenho o benefício de correr riscos ao longo do caminho misto do projeto Scala / Java. E mesmo que eu pudesse optar por fazer um projeto misto, o item 7 é essencial para permitir que eu pegue problemas de tempo de compilação se / quando adiciono / removo membros da enumeração ou estou escrevendo algum novo código para lidar com os membros existentes da enumeração.
B) Usando o padrão " 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}
}
Os seguintes itens da definição de enumeração não estão disponíveis:
É discutível que ele realmente atenda aos itens de definição de enumeração 5 e 6. Para 5, é muito difícil afirmar que é eficiente. Para 6, não é realmente fácil estender para armazenar dados adicionais associados a singleton.
C) Usando o scala.Enumeration
padrão (inspirado nesta resposta do 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]
}
Os seguintes itens da definição de enumeração não estão disponíveis (é idêntico à lista para usar diretamente o Java Enum):
Novamente para meus projetos atuais, o item 7 é fundamental para permitir que eu pegue problemas de tempo de compilação se / quando adiciono / removo membros da enumeração ou estou escrevendo algum novo código para lidar com os membros existentes da enumeração.
Portanto, dada a definição acima de uma enumeração, nenhuma das três soluções acima funciona, pois elas não fornecem tudo descrito na definição de enumeração acima:
Cada uma dessas soluções pode ser retrabalhada / expandida / refatorada para tentar cobrir alguns dos requisitos ausentes de cada um. No entanto, nem o Java Enum
nem as scala.Enumeration
soluções podem ser suficientemente expandidas para fornecer o item 7. E para meus próprios projetos, esse é um dos valores mais convincentes do uso de um tipo fechado no Scala. Eu prefiro fortemente avisos / erros de tempo de compilação para indicar que tenho uma lacuna / problema no meu código, em vez de precisar coletá-lo de uma exceção / falha no tempo de execução da produção.
Nesse sentido, comecei a trabalhar com o case object
caminho para ver se conseguia produzir uma solução que cobrisse toda a definição de enumeração acima. O primeiro desafio foi avançar no núcleo do problema de inicialização de classe / objeto da JVM (abordado em detalhes nesta postagem do StackOverflow ). E finalmente consegui descobrir uma solução.
Como minha solução são duas características; Enumeration and EnumerationDecorated , e como a Enumeration
característica tem mais de 400 linhas (muitos comentários explicando o contexto), deixo de colar o texto nesse segmento (o que o faria esticar a página consideravelmente). Para detalhes, pule diretamente para o Gist .
Eis como a solução acaba usando a mesma ideia de dados acima (versão totalmente comentada disponível aqui ) e implementada no 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 é um exemplo de uso de um novo par de traços de enumeração que eu criei (localizado nesta lista ) para implementar todos os recursos desejados e descritos na definição de enumeração.
Uma preocupação expressa é que os nomes dos membros da enumeração devem ser repetidos ( decorationOrderedSet
no exemplo acima). Embora eu o minimizasse até uma única repetição, não conseguia ver como torná-lo ainda menos devido a dois problemas:
getClass.getDeclaredClasses
tem uma ordem indefinida (e é improvável que esteja na mesma ordem que as case object
declarações no código-fonte)Dadas essas duas questões, tive que desistir de tentar gerar uma ordem implícita e exigir explicitamente que o cliente a definisse e a declarasse com algum tipo de noção de conjunto ordenada. Como as coleções Scala não possuem uma implementação de conjunto ordenada por inserção, o melhor que pude fazer foi usar uma List
verificação de tempo de execução e, em seguida, verificar se realmente era um conjunto. Não é assim que eu teria preferido ter conseguido isso.
E dado o design exigido para a segunda ordem de lista / conjunto val
, conforme o ChessPiecesEnhancedDecorated
exemplo acima, foi possível adicionar case object PAWN2 extends Member
e depois esquecer de adicionar Decoration(PAWN2,'P2', 2)
a decorationOrderedSet
. Portanto, há uma verificação de tempo de execução para verificar se a lista não é apenas um conjunto, mas contém TODOS os objetos de caso que estendem o arquivo sealed trait Member
. Essa foi uma forma especial de reflexão / macro inferno para resolver.
Por favor, deixe comentários e / ou feedback sobre o Gist .
org.scalaolio.util.Enumeration
e org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Objetos de caso já retornam seus nomes para seus métodos toString, portanto, passá-los separadamente é desnecessário. Aqui está uma versão semelhante à do jho (métodos de conveniência omitidos por questões de brevidade):
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)
}
Objetos são preguiçosos; usando vals, podemos descartar a lista, mas precisamos repetir o 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 você não se importa com trapaças, pode pré-carregar seus valores de enumeração usando a API de reflexão ou algo como o Google Reflections. Objetos de caso não preguiçosos oferecem a sintaxe mais limpa:
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
}
Agradável e limpo, com todas as vantagens de classes de casos e enumerações Java. Pessoalmente, defino os valores de enumeração fora do objeto para corresponder melhor ao código idiomático do Scala:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, só recebo de volta os valores acessados anteriormente. Existe alguma maneira de contornar isso?
As vantagens de usar classes de caso sobre enumerações são:
As vantagens de usar enumerações em vez de classes de caso são:
Portanto, em geral, se você só precisa de uma lista de constantes simples por nome, use enumerações. Caso contrário, se você precisar de algo um pouco mais complexo ou desejar a segurança extra do compilador, informando se todas as correspondências foram especificadas, use classes de casos.
ATUALIZAÇÃO: O código abaixo possui um erro, descrito aqui . O programa de teste abaixo funciona, mas se você usasse o DayOfWeek.Mon (por exemplo) antes do DayOfWeek, ele falharia porque o DayOfWeek não foi inicializado (o uso de um objeto interno não faz com que um objeto externo seja inicializado). Você ainda pode usar esse código se fizer algo como val enums = Seq( DayOfWeek )
na sua classe principal, forçando a inicialização de suas enumerações ou usar as modificações do caotic3quilibrium. Ansioso para um enum baseado em macro!
Se você quiser
então o seguinte pode ser de seu interesse. Feedback bem-vindo.
Nesta implementação, existem classes básicas abstratas Enum e EnumVal, que você estende. Veremos essas classes em um minuto, mas primeiro, veja como você definiria uma enumeração:
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()
}
Observe que você precisa usar cada valor de enum (chame seu método de aplicação) para dar vida a ele. [Eu gostaria que os objetos internos não fossem preguiçosos, a menos que eu pedisse especificamente que eles fossem. Eu acho que.]
É claro que poderíamos adicionar métodos / dados ao DayOfWeek, Val ou aos objetos de caso individuais, se assim o desejássemos.
E aqui está como você usaria essa enumeração:
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"
}
}
Aqui está o que você obtém quando o compila:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Você pode substituir "correspondência do dia" por "correspondência do dia: @unchecked)" onde não deseja esses avisos ou simplesmente incluir um caso abrangente no final.
Quando você executa o programa acima, obtém esta saída:
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
Observe que, como a Lista e os Mapas são imutáveis, você pode remover facilmente elementos para criar subconjuntos, sem interromper a enumeração em si.
Aqui está a própria classe Enum (e EnumVal dentro dela):
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 )
}
}
}
E aqui está um uso mais avançado dele que controla os IDs e adiciona dados / métodos à abstração Val e à própria enumeração:
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
] é um pecado mortal limítrofe no mundo das PF" - não acho que essa opinião seja universalmente aceita.
Eu tenho uma boa e simples lib aqui que permite que você use características / classes seladas como valores de enumeração sem precisar manter sua própria lista de valores. Ele se baseia em uma macro simples que não depende do buggy knownDirectSubclasses
.
Atualização em março de 2017: conforme comentado por Anthony Accioly , o scala.Enumeration/enum
PR foi fechado.
Dotty (compilador de próxima geração para Scala) assumirá a liderança, embora a edição dotty de 1970 e o PR 1958 de Martin Odersky .
Nota: existe agora (agosto de 2016, mais de 6 anos depois) uma proposta para remover scala.Enumeration
: PR 5352
Descontinuar
scala.Enumeration
, adicionar@enum
anotaçãoA sintaxe
@enum
class Toggle {
ON
OFF
}
é um possível exemplo de implementação, a intenção é também oferecer suporte a ADTs que estejam em conformidade com certas restrições (sem aninhamento, recursão ou variação de parâmetros do construtor), por exemplo:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Preteri o desastre não mitigado que é
scala.Enumeration
.Vantagens do @enum sobre o scala.
- Realmente funciona
- Interoperabilidade Java
- Sem problemas de apagamento
- Sem mini-DSL confuso para aprender ao definir enumerações
Desvantagens: Nenhuma.
Isso resolve o problema de não poder ter uma base de código que suporte Scala-JVM
Scala.js
e Scala-Native (código-fonte Java não suportadoScala.js/Scala-Native
, código-fonte Scala não é capaz de definir enumerações que são aceitas pelas APIs existentes no Scala-JVM).
Outra desvantagem de classes de caso versus enumerações, quando você precisará iterar ou filtrar todas as instâncias. Esse é um recurso interno da Enumeração (e também das enumerações Java), enquanto as classes de caso não suportam automaticamente esse recurso.
Em outras palavras: "não há uma maneira fácil de obter uma lista do conjunto total de valores enumerados com classes de caso".
Se você é sério em manter a interoperabilidade com outras linguagens da JVM (por exemplo, Java), a melhor opção é escrever enumerações Java. Eles funcionam de forma transparente a partir dos códigos Scala e Java, o que é mais do que pode ser dito para scala.Enumeration
objetos ou caso. Não vamos ter uma nova biblioteca de enumerações para cada novo projeto de hobby no GitHub, se puder ser evitado!
Eu já vi várias versões de fazer uma classe de caso imitar uma enumeração. Aqui está a minha versão:
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)
}
}
O que permite construir classes de caso que se parecem com o seguinte:
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)
}
Talvez alguém possa inventar um truque melhor do que simplesmente adicionar uma classe de cada caso à lista, como eu fiz. Isso foi tudo o que pude apresentar na época.
Estive indo e voltando nessas duas opções nas últimas vezes em que precisei delas. Até recentemente, minha preferência era pela opção de característica / objeto de caso selado.
1) Declaração de Enumeração Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Traços selados + objetos de caso
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Embora nenhum deles realmente atenda a tudo o que uma enumeração java oferece, abaixo estão os prós e contras:
Enumeração Scala
Prós: -Funções para instanciar com opção ou assumir diretamente precisão (mais fácil ao carregar de um armazenamento persistente) -Iteração sobre todos os valores possíveis
Contras: -O aviso de compilação para pesquisa não exaustiva não é suportado (torna a correspondência de padrões menos ideal)
Objetos de caso / traços selados
Prós: -Usando traços selados, podemos pré-instanciar alguns valores, enquanto outros podem ser injetados no momento da criação -suporte completo para correspondência de padrões (métodos de aplicação / não aplicação definidos)
Contras: -Instanciando de um armazenamento persistente - você geralmente precisa usar a correspondência de padrões aqui ou definir sua própria lista de todos os possíveis 'valores de enumeração'
O que finalmente me fez mudar de opinião foi algo como o seguinte trecho:
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
}
As .get
chamadas eram hediondas - usando enumeração, em vez disso, posso simplesmente chamar o método withName na enumeração da seguinte maneira:
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)
}
}
Portanto, acho que minha preferência daqui para frente é usar Enumerações quando os valores tiverem a intenção de serem acessados de um repositório e objetos de caso / traços fechados, caso contrário.
Eu prefiro case objects
(é uma questão de preferência pessoal). Para lidar com os problemas inerentes a essa abordagem (analisar a sequência e repetir todos os elementos), adicionei algumas linhas que não são perfeitas, mas são eficazes.
Estou colando o código aqui, esperando que possa ser útil e também que outros possam aprimorá-lo.
/**
* 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 aqueles que ainda desejam obter a resposta do GatesDa para o trabalho : você pode apenas fazer referência ao objeto de caso após declará-lo para instanciar:
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
}
Eu acho que a maior vantagem de ter case classes
mais enumerations
é que você pode usar o padrão de classe de tipo, também conhecido como polimorfismo ad-hoc . Não precisa corresponder enumerações como:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
em vez disso, você terá 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() = ...
}