Apakah ada pedoman praktik terbaik tentang kapan menggunakan kelas kasus (atau objek kasus) vs memperpanjang Pencacahan di Scala?
Mereka tampaknya menawarkan beberapa manfaat yang sama.
enum
(untuk pertengahan 2020).
Apakah ada pedoman praktik terbaik tentang kapan menggunakan kelas kasus (atau objek kasus) vs memperpanjang Pencacahan di Scala?
Mereka tampaknya menawarkan beberapa manfaat yang sama.
enum
(untuk pertengahan 2020).
Jawaban:
Satu perbedaan besar adalah bahwa ia Enumeration
datang dengan dukungan untuk membuat mereka dari beberapa name
String. Sebagai contoh:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Maka Anda dapat melakukan:
val ccy = Currency.withName("EUR")
Ini berguna ketika ingin melanjutkan enumerasi (misalnya, ke database) atau membuatnya dari data yang berada di file. Namun, saya menemukan secara umum bahwa enumerasi agak canggung di Scala dan memiliki rasa add-on yang canggung, jadi saya sekarang cenderung menggunakan case object
s. A case object
lebih fleksibel daripada enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Jadi sekarang saya memiliki keunggulan ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Seperti yang ditunjukkan @ chaotic3quilibrium (dengan beberapa koreksi untuk memudahkan membaca):
Mengenai pola "UnknownCurrency (kode)", ada cara lain untuk menangani tidak menemukan string kode mata uang selain "melanggar" sifat set tertutup dari
Currency
jenis tersebut.UnknownCurrency
menjadi tipeCurrency
sekarang dapat menyelinap ke bagian lain dari API.Dianjurkan untuk mendorong kasus itu ke luar
Enumeration
dan membuat klien berurusan denganOption[Currency]
jenis yang jelas akan menunjukkan benar-benar ada masalah yang cocok dan "mendorong" pengguna API untuk mengatasinya sendiri.
Untuk menindaklanjuti jawaban lain di sini, kelemahan utama case object
s over Enumeration
adalah:
Tidak dapat mengulangi semua contoh "enumerasi" . Ini memang masalahnya, tetapi dalam praktiknya sangat jarang hal ini diperlukan.
Tidak dapat membuat instantiasi dengan mudah dari nilai yang ada . Ini juga benar tetapi, kecuali dalam kasus enumerasi besar (misalnya, semua mata uang), ini tidak menghadirkan overhead yang besar.
trade.ccy
dengan contoh sifat yang disegel.
case
object
menghasilkan jejak kode yang lebih besar (~ 4x) dari Enumeration
? Perbedaan yang berguna terutama untuk scala.js
proyek - proyek yang membutuhkan jejak kecil.
UPDATE: Solusi berbasis makro baru telah dibuat yang jauh lebih unggul daripada solusi yang saya uraikan di bawah ini. Saya sangat merekomendasikan menggunakan solusi berbasis makro baru ini . Dan tampaknya rencana untuk Dotty akan membuat gaya solusi enum bagian dari bahasa. Whoohoo!
Ringkasan:
Ada tiga pola dasar untuk mencoba mereproduksi Jawa Enum
dalam proyek Scala. Dua dari tiga pola; langsung menggunakan Java Enum
dan scala.Enumeration
, tidak mampu mengaktifkan pencocokan pola lengkap Scala. Dan yang ketiga; "disegel sifat + objek kasus", memang ... tetapi memiliki inisialisasi kelas / objek JVM yang menghasilkan generasi indeks ordinal yang tidak konsisten.
Saya telah menciptakan solusi dengan dua kelas; Enumeration and EnumerationDecorated , terletak di Intisari ini . Saya tidak memposting kode ke utas ini karena file untuk Enumerasi cukup besar (+400 baris - berisi banyak komentar yang menjelaskan konteks implementasi).
Detail:
Pertanyaan yang Anda ajukan cukup umum; "... kapan menggunakan case
kelasobjects
vs memperluas [scala.]Enumeration
". Dan ternyata ada BANYAK kemungkinan jawaban, masing-masing jawaban tergantung pada seluk-beluk persyaratan proyek spesifik yang Anda miliki. Jawabannya dapat direduksi menjadi tiga pola dasar.
Untuk memulai, mari kita pastikan kita bekerja dari ide dasar yang sama tentang apa itu enumerasi. Mari kita mendefinisikan enumerasi sebagian besar dalam hal yang Enum
disediakan pada Java 5 (1.5) :
Enum
, akan lebih baik untuk dapat secara eksplisit meningkatkan kecocokan pola pencocokan Scala untuk enumerasi Selanjutnya, mari kita lihat versi tiga pola solusi yang paling umum diposting:
A) Sebenarnya langsung menggunakan pola JavaEnum
(dalam proyek Scala / Java campuran):
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;
}
}
Item berikut dari definisi enumerasi tidak tersedia:
Untuk proyek saya saat ini, saya tidak mendapat manfaat dari mengambil risiko di sekitar jalur proyek campuran Scala / Java. Dan bahkan jika saya bisa memilih untuk melakukan proyek campuran, item 7 sangat penting untuk memungkinkan saya menangkap masalah waktu kompilasi jika / ketika saya menambah / menghapus anggota enumerasi, atau sedang menulis beberapa kode baru untuk menangani anggota enumerasi yang ada.
B) Menggunakan pola " 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}
}
Item berikut dari definisi enumerasi tidak tersedia:
Dapat diperdebatkan bahwa itu benar-benar memenuhi item definisi enumerasi 5 dan 6. Untuk 5, sangat sulit untuk mengklaim itu efisien. Untuk 6, itu tidak mudah untuk memperluas untuk menyimpan data terkait singleton-ness tambahan.
C) Menggunakan scala.Enumeration
pola (terinspirasi oleh jawaban StackOverflow ini ):
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]
}
Item berikut dari definisi enumerasi tidak tersedia (kebetulan identik dengan daftar untuk langsung menggunakan Java Enum):
Sekali lagi untuk proyek saya saat ini, item 7 sangat penting untuk memungkinkan saya menangkap masalah waktu kompilasi jika / ketika saya menambah / menghapus anggota enumerasi, atau sedang menulis beberapa kode baru untuk berurusan dengan anggota enumerasi yang ada.
Jadi, mengingat definisi enumerasi di atas, tidak satu pun dari tiga solusi di atas yang berfungsi karena mereka tidak memberikan semua yang diuraikan dalam definisi enumerasi di atas:
Masing-masing solusi ini pada akhirnya dapat dikerjakan ulang / diperluas / di refactored untuk mencoba menutupi beberapa kebutuhan yang hilang dari masing-masing. Namun, baik Java Enum
maupun scala.Enumeration
solusi tidak dapat diperluas secara memadai untuk menyediakan item 7. Dan untuk proyek saya sendiri, ini adalah salah satu nilai yang lebih menarik dari penggunaan tipe tertutup dalam Scala. Saya sangat suka mengkompilasi peringatan waktu / kesalahan untuk menunjukkan bahwa saya memiliki celah / masalah dalam kode saya sebagai lawan harus mengumpulkannya dari pengecualian / kegagalan runtime produksi.
Dalam hal itu, saya mulai bekerja dengan case object
jalur untuk melihat apakah saya bisa menghasilkan solusi yang mencakup semua definisi enumerasi di atas. Tantangan pertama adalah mendorong inti dari masalah inisialisasi kelas / objek JVM (dibahas secara rinci dalam posting StackOverflow ini ). Dan saya akhirnya bisa menemukan solusi.
Karena solusi saya adalah dua sifat; Enumeration dan EnumerationDecorated , dan karena Enumeration
sifatnya lebih dari +400 baris (banyak komentar yang menjelaskan konteks), saya tidak mau menempelkannya ke dalam utas ini (yang akan membuatnya meregangkan halaman secara serius). Untuk detailnya, silakan langsung menuju Intisari .
Inilah solusi yang akhirnya tampak seperti menggunakan ide data yang sama seperti di atas (versi yang sepenuhnya dikomentari tersedia di sini ) dan diimplementasikan dalam 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
}
Ini adalah contoh penggunaan sepasang sifat enumerasi baru yang saya buat (terletak di Intisari ini ) untuk mengimplementasikan semua kemampuan yang diinginkan dan diuraikan dalam definisi enumerasi.
Satu keprihatinan yang diungkapkan adalah bahwa nama anggota enumerasi harus diulang ( decorationOrderedSet
dalam contoh di atas). Sementara saya meminimalkannya menjadi satu pengulangan, saya tidak bisa melihat bagaimana membuatnya lebih sedikit karena dua masalah:
getClass.getDeclaredClasses
memiliki urutan yang tidak ditentukan (dan sangat tidak mungkin berada dalam urutan yang sama dengan case object
deklarasi dalam kode sumber)Dengan adanya dua masalah ini, saya harus berhenti berusaha untuk menghasilkan pemesanan tersirat dan harus secara eksplisit mengharuskan klien menentukan dan menyatakannya dengan semacam gagasan kumpulan pesanan. Karena koleksi Scala tidak memiliki implementasi implementasi urutan yang disisipkan, yang terbaik yang bisa saya lakukan adalah menggunakan List
dan kemudian memeriksa runtime apakah itu benar-benar sebuah set. Bukan bagaimana saya lebih suka untuk mencapai ini.
Dan mengingat desain yang diperlukan daftar kedua / set pemesanan ini val
, mengingat ChessPiecesEnhancedDecorated
contoh di atas, adalah mungkin untuk menambahkan case object PAWN2 extends Member
dan kemudian lupa untuk menambahkan Decoration(PAWN2,'P2', 2)
ke decorationOrderedSet
. Jadi, ada pemeriksaan runtime untuk memverifikasi bahwa daftar tersebut tidak hanya satu set, tetapi berisi SEMUA objek kasus yang memperpanjang sealed trait Member
. Itu adalah bentuk khusus dari refleksi / makro neraka untuk dikerjakan.
Silakan tinggalkan komentar dan / atau umpan balik pada Intisari .
org.scalaolio.util.Enumeration
dan org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Objek case sudah mengembalikan nama mereka untuk metode toString mereka, jadi meneruskannya secara terpisah tidak perlu. Berikut adalah versi yang mirip dengan jho (metode kenyamanan dihilangkan untuk singkatnya):
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)
}
Objek malas; dengan menggunakan vals, kita dapat menjatuhkan daftar tetapi harus mengulangi namanya:
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") {}
}
Jika Anda tidak keberatan melakukan kecurangan, Anda dapat memuat nilai enumerasi menggunakan API refleksi atau sesuatu seperti Google Refleksi. Objek kasing yang tidak malas memberi Anda sintaks terbersih:
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
}
Bagus dan bersih, dengan semua keunggulan kelas kasus dan enumerasi Java. Secara pribadi, saya mendefinisikan nilai enumerasi di luar objek agar lebih cocok dengan kode Scala idiomatik:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, saya hanya mendapatkan kembali nilai yang telah saya akses sebelumnya. Apakah ada jalan keluarnya?
Keuntungan menggunakan kelas kasus dibandingkan Enumerasi adalah:
Keuntungan menggunakan Enumerasi daripada kelas kasus adalah:
Jadi secara umum, jika Anda hanya perlu daftar konstanta sederhana dengan nama, gunakan enumerasi. Kalau tidak, jika Anda membutuhkan sesuatu yang sedikit lebih kompleks atau ingin keamanan tambahan dari kompiler memberi tahu Anda jika Anda memiliki semua kecocokan yang ditentukan, gunakan kelas kasus.
UPDATE: Kode di bawah ini memiliki bug, dijelaskan di sini . Program pengujian di bawah ini berfungsi, tetapi jika Anda menggunakan DayOfWeek.Mon (misalnya) sebelum DayOfWeek itu sendiri, itu akan gagal karena DayOfWeek belum diinisialisasi (penggunaan objek dalam tidak menyebabkan objek luar diinisialisasi). Anda masih dapat menggunakan kode ini jika Anda melakukan sesuatu seperti val enums = Seq( DayOfWeek )
di kelas utama Anda, memaksa inisialisasi enum Anda, atau Anda dapat menggunakan modifikasi chaotic3quilibrium. Menantikan enum berbasis makro!
jika kamu mau
maka yang berikut ini mungkin menarik. Umpan balik.
Dalam implementasi ini ada kelas dasar Enum dan EnumVal abstrak, yang Anda memperpanjang. Kita akan melihat kelas-kelas itu sebentar lagi, tetapi pertama-tama, inilah cara Anda mendefinisikan 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()
}
Perhatikan bahwa Anda harus menggunakan setiap nilai enum (panggil metode yang berlaku) untuk menghidupkannya. [Aku berharap benda dalam tidak malas kecuali aku secara khusus memintanya. Kupikir.]
Kita tentu saja dapat menambahkan metode / data ke DayOfWeek, Val, atau objek kasus individual jika kita inginkan.
Dan inilah cara Anda menggunakan enum seperti itu:
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"
}
}
Inilah yang Anda dapatkan ketika Anda mengompilasinya:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Anda dapat mengganti "pertandingan hari" dengan "pertandingan" (hari: @ cek ulang) "di mana Anda tidak ingin peringatan seperti itu, atau cukup sertakan kotak semua kasus di akhir.
Ketika Anda menjalankan program di atas, Anda mendapatkan output ini:
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
Perhatikan bahwa karena Daftar dan Peta tidak dapat diubah, Anda dapat dengan mudah menghapus elemen untuk membuat subset, tanpa merusak enum itu sendiri.
Ini adalah kelas Enum itu sendiri (dan EnumVal di dalamnya):
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 )
}
}
}
Dan di sini adalah penggunaan yang lebih maju yang mengontrol ID dan menambahkan data / metode ke abstraksi Val dan enum itu sendiri:
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
] adalah dosa berat batas di dunia FP" - Saya tidak berpikir bahwa pendapat diterima secara universal.
Saya memiliki lib sederhana yang bagus di sini yang memungkinkan Anda untuk menggunakan sifat / kelas yang disegel sebagai nilai enum tanpa harus mempertahankan daftar nilai Anda sendiri. Itu bergantung pada makro sederhana yang tidak bergantung pada buggy knownDirectSubclasses
.
Pembaruan Maret 2017: seperti yang dikomentari oleh Anthony Accioly , scala.Enumeration/enum
PR telah ditutup.
Dotty (kompiler generasi berikutnya untuk Scala) akan memimpin, meskipun edisi dotty 1970 dan PR 1958 Martin Odersky .
Catatan: sekarang ada (Agustus 2016, 6+ tahun kemudian) proposal untuk dihapus scala.Enumeration
: PR 5352
Jelek
scala.Enumeration
, tambahkan@enum
anotasiSintaksnya
@enum
class Toggle {
ON
OFF
}
adalah contoh implementasi yang mungkin, maksudnya adalah untuk juga mendukung ADT yang sesuai dengan batasan tertentu (tidak ada nesting, rekursi, atau beragam parameter konstruktor), misalnya:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Mengecewakan bencana yang tidak dikurangi itu
scala.Enumeration
.Keuntungan dari @enum lebih dari scala. Penghitungan:
- Sebenarnya berhasil
- Java interop
- Tidak ada masalah penghapusan
- Tidak ada mini-DSL yang membingungkan untuk dipelajari saat mendefinisikan enumerasi
Kekurangan: Tidak ada.
Ini mengatasi masalah tidak dapat memiliki satu basis kode yang mendukung Scala-JVM,
Scala.js
dan Scala-Native (kode sumber Java tidak didukungScala.js/Scala-Native
, kode sumber Scala tidak dapat mendefinisikan enum yang diterima oleh API yang ada pada Scala-JVM).
Kerugian lain dari kelas kasus versus Enumerasi ketika Anda harus mengulang atau memfilter semua contoh. Ini adalah kemampuan Enumerasi bawaan (dan enum Java juga) sementara kelas kasus tidak secara otomatis mendukung kemampuan tersebut.
Dengan kata lain: "tidak ada cara mudah untuk mendapatkan daftar set total nilai yang disebutkan dengan kelas kasus".
Jika Anda serius mempertahankan interoperabilitas dengan bahasa JVM lainnya (mis. Java) maka opsi terbaik adalah menulis Java enum. Mereka bekerja secara transparan dari kedua Scala dan kode Java, yang lebih dari yang bisa dikatakan untuk scala.Enumeration
atau objek kasus. Mari kita tidak memiliki perpustakaan enumerasi baru untuk setiap proyek hobi baru di GitHub, jika itu bisa dihindari!
Saya telah melihat berbagai versi membuat kelas kasus meniru enumerasi. Ini versi saya:
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)
}
}
Yang memungkinkan Anda untuk membangun kelas kasus yang terlihat seperti berikut:
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)
}
Mungkin seseorang bisa membuat trik yang lebih baik daripada hanya menambahkan kelas kasus masing-masing ke daftar seperti yang saya lakukan. Hanya itu yang bisa saya pikirkan saat itu.
Saya telah bolak-balik pada dua opsi ini beberapa kali terakhir saya membutuhkannya. Hingga baru-baru ini, preferensi saya adalah untuk opsi objek sifat / kasus tersegel.
1) Deklarasi Pencacahan Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Ciri Tertutup + Objek Kasus
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Meskipun tidak satu pun dari ini benar-benar memenuhi semua yang diberikan enumerasi java kepada Anda, di bawah ini adalah pro dan kontra:
Pencacahan Scala
Kelebihan: -Fungsi untuk instantiating dengan opsi atau langsung mengasumsikan akurat (lebih mudah saat memuat dari toko yang persisten) -Iterasi atas semua nilai yang mungkin didukung
Kekurangan: -Peringatan kompilasi untuk pencarian yang tidak lengkap tidak didukung (membuat pencocokan pola kurang ideal)
Objek Kasus / Ciri tertutup
Kelebihan: -Menggunakan ciri-ciri tersegel, kita dapat melakukan pra-instantiasi beberapa nilai sementara yang lain dapat disuntikkan pada waktu pembuatan -Dukungan penuh untuk pencocokan pola (berlaku / metode tidak berlaku didefinisikan)
Cons: -Instantiating dari toko persisten - Anda sering harus menggunakan pencocokan pola di sini atau menentukan daftar Anda sendiri dari semua 'enum values' yang mungkin
Apa yang akhirnya membuat saya mengubah pendapat saya adalah seperti cuplikan berikut:
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
}
The .get
panggilan yang mengerikan - menggunakan pencacahan bukan saya hanya bisa memanggil metode withName pada pencacahan sebagai berikut:
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)
}
}
Jadi saya pikir preferensi saya ke depan adalah menggunakan Enumerasi ketika nilai-nilai dimaksudkan untuk diakses dari repositori dan objek kasus / sifat tertutup jika tidak.
Saya lebih suka case objects
(ini masalah preferensi pribadi). Untuk mengatasi masalah yang melekat pada pendekatan itu (parse string dan iterate atas semua elemen), saya telah menambahkan beberapa baris yang tidak sempurna, tetapi efektif.
Saya menempelkan Anda kode di sini mengharapkan itu bisa bermanfaat, dan juga bahwa orang lain dapat memperbaikinya.
/**
* 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 }
}
Bagi mereka yang masih mencari cara untuk mendapatkan jawaban GatesDa untuk bekerja : Anda bisa mereferensikan objek kasus setelah mendeklarasikannya untuk membuat instance:
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
}
Saya pikir keuntungan terbesar dari memiliki case classes
lebih dari itu enumerations
adalah Anda dapat menggunakan pola tipe class alias ad-hoc polymorphysm . Tidak perlu mencocokkan enum seperti:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
alih-alih, Anda akan memiliki sesuatu seperti:
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() = ...
}