Scala'da Numaralandırmayı genişletme ile vaka sınıflarının (veya vaka nesnelerini) ne zaman kullanacağınıza dair en iyi uygulama yönergeleri var mı?
Aynı faydalardan bazılarını sunuyor gibi görünüyor.
enum
(2020 ortası için).
Scala'da Numaralandırmayı genişletme ile vaka sınıflarının (veya vaka nesnelerini) ne zaman kullanacağınıza dair en iyi uygulama yönergeleri var mı?
Aynı faydalardan bazılarını sunuyor gibi görünüyor.
enum
(2020 ortası için).
Yanıtlar:
Büyük bir fark, Enumeration
bazı name
String'den onları somutlaştırmak için destekle gelmesidir. Örneğin:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Sonra şunları yapabilirsiniz:
val ccy = Currency.withName("EUR")
Bu, numaralandırmalara (örneğin, bir veritabanına) devam etmek veya dosyalarda bulunan verilerden oluşturmak istediğinizde yararlıdır. Bununla birlikte, genel olarak, numaralandırmaların Scala'da biraz sakar olduğunu ve garip bir eklenti hissi olduğunu düşünüyorum, bu yüzden şimdi case object
s kullanma eğilimindeyim . A case object
bir numaradan daha esnektir:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Şimdi avantajım var ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
@ Chaotic3quilibrium'un işaret ettiği gibi (okumayı kolaylaştırmak için bazı düzeltmelerle):
"UnknownCurrency (kod)" kalıbı ile ilgili olarak, türün kapalı küme niteliğini "kırmak" dışında bir para birimi kodu dizesi bulamamanın başka yolları da vardır
Currency
.UnknownCurrency
tür olmasıCurrency
artık bir API'nın diğer bölümlerine gizlenebilir.Bu vakanın dışarıya itilmesi
Enumeration
ve müşterininOption[Currency]
gerçekten eşleşen bir sorun olduğunu açıkça belirten bir türle ilgilenmesi ve API kullanıcısını kendisini sıralaması için "cesaretlendirmesi" önerilir.
Buradaki diğer cevapları takip etmek için, case object
s over Enumeration
s'nin ana dezavantajları şunlardır:
"Numaralandırma" nın tüm örnekleri üzerinde yineleme yapılamaz . Bu kesinlikle doğru, ama pratikte bunun gerekli olduğunu çok nadir buldum.
Kalıcı değerden kolayca somutlaştırılamaz . Bu da geçerlidir, ancak, büyük numaralandırmalar (örneğin, tüm para birimleri) hariç, bu büyük bir ek yük oluşturmaz.
trade.ccy
Mühürlü özellik örneğinde eşleşen öğenin türünü anlamıyorum .
case
object
daha büyük (~ 4x) kod kapladığı alan oluşturmaz Enumeration
mı? Özellikle scala.js
az yer kaplayan projeler için yararlı bir ayrım .
GÜNCELLEME: Aşağıdaki ana hatları çizdiğim çözümden çok daha üstün olan yeni bir makro tabanlı çözüm oluşturuldu. Bu yeni makro tabanlı çözümü kullanmanızı önemle tavsiye ederim . Ve Dotty için planların bu tarz enum çözümü dilinin bir parçası haline getireceği planları görünüyor. Whoohoo!
Özet:
Java'yı Enum
bir Scala projesi içinde yeniden üretmeye çalışmak için üç temel model vardır . Üç modelden ikisi; doğrudan Java kullanarak Enum
ve scala.Enumeration
Scala'nın ayrıntılı desen eşleştirmesini etkinleştiremez. Üçüncüsü; "mühürlü özellik + vaka nesnesi", ... ancak JVM sınıfı / nesne başlatma komplikasyonlarına sahip ve bu da tutarsız sıralı dizin oluşturma ile sonuçlanıyor.
İki sınıfla bir çözüm yarattım; Sayım ve EnumerationDecorated , bu yer Gist'e . Numaralandırma dosyası oldukça büyük olduğu için bu iş parçacığının kodunu yayınlamadım (+400 satır - uygulama bağlamını açıklayan birçok yorum içerir).
Ayrıntılar:
Sorduğunuz soru oldukça genel; "... case
sınıflar ne zaman objects
uzayacak? [scala.]Enumeration
" Ve her bir cevabın sahip olduğunuz belirli proje gereksinimlerinin inceliklerine bağlı olarak ÇOK olası cevaplar olduğu ortaya çıkıyor. Cevap üç temel desene indirgenebilir.
Başlamak için, bir numaralandırmanın ne olduğu hakkında aynı temel fikirden çalıştığımızdan emin olalım. Bir numaralandırmayı daha çok Enum
Java 5 (1.5) 'den sağlanan şekilde tanımlayalım :
Enum
, bir numaralandırma için Scala'nın desen eşleştirme yorgunluk kontrolünü açıkça kullanabilmek güzel olurdu. Ardından, yayınlanan en yaygın üç çözüm modelinin haşlanmış sürümlerine bakalım:
A) Aslında doğrudan JavaEnum
desenini kullanarak (karışık bir Scala / Java projesinde):
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;
}
}
Numaralandırma tanımından aşağıdaki öğeler mevcut değildir:
Mevcut projelerim için, Scala / Java karma proje yolunda risk almak gibi bir avantajım yok. Karışık bir proje yapmayı seçebilsem bile, numaralandırma üyeleri eklediğimde / kaldırdığımda veya mevcut numaralandırma üyeleriyle başa çıkmak için yeni bir kod yazıyorsam, öğe 7 derleme zamanı sorunlarını yakalamam için kritik öneme sahip.
B) " sealed trait
+case objects
" desenini kullanarak :
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}
}
Numaralandırma tanımından aşağıdaki öğeler mevcut değildir:
Numaralandırma tanım öğelerinin 5 ve 6'yı gerçekten karşılaması tartışılabilir. 5 için, etkili olduğunu iddia etmek bir esneme. 6 için, ek ilişkili tekli veri elde etmek için genişletmek gerçekten kolay değil.
C)scala.Enumeration
Deseni kullanma ( bu StackOverflow yanıtından esinlenerek ):
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]
}
Numaralandırma tanımından aşağıdaki öğeler kullanılamaz (doğrudan Java Numaralandırma'yı kullanmak için listeyle aynıdır):
Yine mevcut projelerim için, madde 7, numaralandırma üyeleri eklediğimde / kaldırdığımda veya mevcut numaralandırma üyeleriyle başa çıkmak için yeni bir kod yazdığımda derleme zamanı sorunlarını yakalamama izin vermek için kritik önem taşıyor.
Dolayısıyla, yukarıdaki bir numaralandırma tanımı göz önüne alındığında, yukarıdaki üç çözümün hiçbiri yukarıdaki numaralandırma tanımında özetlenen her şeyi sağlamadığından işe yaramaz:
Bu çözümlerin her biri sonunda, her birinin eksik gereksinimlerinin bir kısmını karşılamaya çalışmak için yeniden işlenebilir / genişletilebilir / yeniden düzenlenebilir. Bununla birlikte, ne Java Enum
ne de scala.Enumeration
çözümler, madde 7'yi sağlamak için yeterince genişletilemez. Ve kendi projelerim için, bu, Scala'da kapalı bir tür kullanmanın daha çekici değerlerinden biridir. Ben bir üretim çalışma zamanı istisna / başarısızlık dışarı çıkarmak zorunda aksine benim kodda bir boşluk / sorun olduğunu belirtmek için zaman uyarıları / hataları derleme tercih ederim.
Bu bağlamda, case object
yukarıdaki tüm numaralandırma tanımını kapsayan bir çözüm üretip üretemeyeceğimi görmek için yolla çalışmaya başladım. İlk zorluk, JVM sınıfı / nesne başlatma sorununun özünü ( bu StackOverflow yayınında ayrıntılı olarak ele alınmıştır) zorlamaktı . Ve sonunda bir çözüm bulabildim.
Benim çözümüm iki özellik olduğu için; Sayım ve EnumerationDecorated , ve o zamandan beri Enumeration
sürekli 400 üzerinde satır uzunluğundadır (bağlamını açıklayan yorumlar, bir sürü), (o sayfayı considerbly aşağı streç yapar) bu konuya yapıştırarak forgoing ediyorum. Ayrıntılar için lütfen doğrudan Gist'e atlayın .
İşte çözüm, yukarıdaki ile aynı veri fikrini ( burada tam olarak yorumlanmış sürüm ) kullanıyor ve uygulanmış gibi görünüyor 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
}
Bu, numaralandırma tanımında istenen ve ana hatlarıyla belirtilen tüm yetenekleri uygulamak için oluşturduğum yeni bir numaralandırma özelliğinin ( bu Gist'te bulunan ) bir çift kullanımının bir örneğidir .
Belirtilen bir endişe, numaralandırma üye adlarının tekrarlanması gerektiğidir ( decorationOrderedSet
yukarıdaki örnekte). Tek bir tekrarı en aza indirgeme rağmen, iki sorun nedeniyle nasıl daha az hale getirileceğini göremedim:
getClass.getDeclaredClasses
tanımlanmamış bir sırası var (ve case object
kaynak kodundaki bildirimlerle aynı sırada olması pek olası değildir )Bu iki sorun göz önüne alındığında, zımni bir sipariş oluşturmaya çalışmaktan vazgeçmek zorunda kaldım ve müşterinin bir tür düzenli set kavramı ile tanımlamasını ve beyan etmesini açıkça zorunlu kılmak zorunda kaldım. Scala koleksiyonları bir insert sipariş seti uygulaması olmadığından, yapabileceğim en iyi şey List
bir set olduğunu ve daha sonra gerçekten bir set olup olmadığını kontrol etmekti. Bunu başarmayı tercih etmem böyle olmazdı.
Ve tasarım bu ikinci liste / set sırasını gerektirdiğinde val
, ChessPiecesEnhancedDecorated
yukarıdaki örnek verildiğinde, eklemek case object PAWN2 extends Member
ve sonra eklemeyi unutmak mümkün Decoration(PAWN2,'P2', 2)
oldu decorationOrderedSet
. Bu nedenle, listenin yalnızca bir küme olmadığını, aynı zamanda genişleten büyük TÜM nesnelerin içerdiğini doğrulamak için bir çalışma zamanı denetimi vardır sealed trait Member
. Bu, üzerinde çalışılacak özel bir yansıma / makro cehennem biçimiydi.
İlgili yorum ve / veya geri bırakınız Gist'e .
org.scalaolio.util.Enumeration
ve org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Büyük / küçük harf nesneleri zaten toString yöntemleri için adlarını döndürür, bu yüzden ayrı ayrı iletmek gereksizdir. Jho'nunkine benzer bir versiyon (kısalık için uygunluk yöntemleri yok):
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)
}
Nesneler tembeldir; vals kullanarak listeyi bırakabiliriz, ancak adı tekrarlamamız gerekir:
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") {}
}
Hile yapmanın bir sakıncası yoksa, yansıma API'sını veya Google Reflections gibi bir şeyi kullanarak numaralandırma değerlerinizi önceden yükleyebilirsiniz. Tembel olmayan vaka nesneleri size en temiz sözdizimini sağlar:
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
}
Vaka sınıflarının ve Java numaralandırmalarının tüm avantajlarıyla güzel ve temiz. Şahsen, idiyomatik Scala koduyla daha iyi eşleşmek için nesne dışındaki numaralandırma değerlerini tanımlarım:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, yalnızca daha önce eriştiğim değerleri geri alırım. Bunun etrafında bir yol var mı?
Vaka sınıflarını Numaralandırmalara göre kullanmanın avantajları şunlardır:
Vaka sınıfları yerine Numaralandırma kullanmanın avantajları şunlardır:
Bu nedenle, genel olarak, ada göre basit sabitler listesine ihtiyacınız varsa, numaralandırma kullanın. Aksi takdirde, biraz daha karmaşık bir şeye ihtiyacınız varsa veya derleyicinin ekstra güvenliğinin belirtilmiş tüm eşleşmelerinizin olup olmadığını size bildirmesini istiyorsanız, vaka sınıflarını kullanın.
GÜNCELLEME: Aşağıdaki kodun burada açıklanan bir hatası vardır . Aşağıdaki test programı çalışır, ancak DayOfWeek'in kendisinden önce DayOfWeek.Mon (örneğin) kullanacak olsaydınız, DayOfWeek başlatılmadığı için başarısız olur (bir iç nesnenin kullanılması bir dış nesnenin başlatılmasına neden olmaz). val enums = Seq( DayOfWeek )
Ana sınıfınızda olduğu gibi bir şey yaparsanız , numaralarınızın başlatılmasını zorlarsanız veya chaotic3quilibrium'un değişikliklerini kullanabilirsiniz. Makro tabanlı bir numaralandırma bekliyorum!
Eğer istersen
o zaman aşağıdakiler ilgi çekici olabilir. Geribildirim hoş geldiniz.
Bu uygulamada, genişlettiğiniz soyut Enum ve EnumVal temel sınıfları vardır. Bu sınıfları bir dakika içinde göreceğiz, ama önce bir numaralandırma şöyle tanımlanacaktı:
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()
}
Her bir enum değerini hayata geçirmek için (uygula yöntemini çağır) kullanmanız gerektiğini unutmayın. [Keşke özellikle olmasını istemedikçe iç nesneler tembel olmasaydı. Bence.]
İstediğimiz takdirde elbette DayOfWeek, Val veya tekil vaka nesnelerine yöntemler / veriler ekleyebiliriz.
İşte böyle bir numaralandırmayı nasıl kullanacaksınız:
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"
}
}
İşte derlediğinizde:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Bu tür uyarıları istemediğiniz "gün eşleşmesi" ni "(gün: @ denetlenen) eşleşmesi" ile değiştirebilir veya sonuna tümünü yakalama vakası ekleyebilirsiniz.
Yukarıdaki programı çalıştırdığınızda, bu çıktıyı alırsınız:
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
Liste ve Haritalar'ın değiştirilemediğinden, numaralandırmayı bozmadan alt kümeler oluşturmak için öğeleri kolayca kaldırabileceğinizi unutmayın.
İşte Enum sınıfının kendisi (ve içindeki EnumVal):
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 )
}
}
}
Ve burada kimlikleri kontrol eden ve Val soyutlamasına ve numaralandırmanın kendisine veri / yöntem ekleyen daha gelişmiş bir kullanım:
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
] FP dünyasında sınırda ölümlü bir günahtır" - Bu fikrin evrensel olarak kabul edildiğini düşünmüyorum.
Burada, kendi değerler listenizi korumak zorunda kalmadan mühürlü özellikleri / sınıfları enum değerleri olarak kullanmanıza izin veren güzel bir basit lib var. Buggy'ye bağlı olmayan basit bir makroya dayanır knownDirectSubclasses
.
Güncelleme Mart 2017: tarafından yorumladı olarak Anthony Accioly , scala.Enumeration/enum
PR kapatıldı.
Dotty (Scala için yeni nesil derleyici), dotty sayı 1970 ve Martin Odersky'nin PR 1958'i yönetecek .
Not: Artık (Ağustos 2016, 6+ yıl sonra) kaldırılacak bir teklif var scala.Enumeration
: PR 5352
Kullanımdan kaldırıldı
scala.Enumeration
, ek@enum
açıklama ekleSözdizimi
@enum
class Toggle {
ON
OFF
}
olası bir uygulama örneğidir, niyet ayrıca belirli kısıtlamalara (yuvalama, özyineleme veya değişen kurucu parametreleri yok) uyan ADT'leri desteklemektir, örneğin:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Zarar görmemiş felaketi ortadan kaldırır
scala.Enumeration
.@Enum'un skalaya göre avantajları.
- Aslında çalışıyor
- Java birlikte çalışma
- Silme sorunu yok
- Numaralandırma tanımlarken öğrenmek için kafa karıştırıcı mini DSL yok
Dezavantajları: Yok.
Bu, Scala-JVM
Scala.js
ve Scala-Native'ı destekleyen bir kod tabanına sahip olamama sorununu giderir (Java kaynak kodu desteklenmezScala.js/Scala-Native
, Scala kaynak kodu Scala-JVM üzerindeki mevcut API'lar tarafından kabul edilen numaralandırmaları tanımlayamaz).
Tüm örneklerde yineleme yapmanız veya filtrelemeniz gerektiğinde, vaka sınıflarının Numaralandırmalara göre bir başka dezavantajı. Bu, yerleşik Sınıflandırma (ve Java numaralandırmalar) özelliğidir, vaka sınıfları bu özelliği otomatik olarak desteklemez.
Başka bir deyişle: "vaka sınıflarıyla birlikte numaralandırılmış toplam değerler kümesinin bir listesini almanın kolay bir yolu yoktur".
Diğer JVM dilleriyle (örneğin Java) birlikte çalışabilirliği sağlama konusunda ciddiyseniz, Java enums yazmak en iyi seçenektir. Bunlar şeffaf bir şekilde hem Scala hem de Java kodundan çalışırlar scala.Enumeration
; Kaçınılabilirse, GitHub'daki her yeni hobi projesi için yeni bir numaralandırma kütüphanesine sahip olmayalım!
Bir vaka sınıfını taklit etmenin çeşitli versiyonlarını gördüm. İşte benim sürüm:
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)
}
}
Bu, aşağıdakine benzeyen vaka sınıfları oluşturmanıza olanak tanır:
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)
}
Belki birisi benim yaptığım gibi listeye her vaka sınıfını eklemekten daha iyi bir numara yapabilirdi. O zaman ortaya çıkabilecek tek şey buydu.
Onlara ihtiyaç duyduğum son birkaç kez bu iki seçenek üzerinde gidip geliyorum. Yakın zamana kadar, tercihim kapalı özellik / vaka nesnesi seçeneği oldu.
1) Scala Numaralandırma Beyanı
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Mühürlü Özellikler + Vaka Nesneleri
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Bunların hiçbiri bir java numaralandırmasının size sunduğu şeylerin hepsini karşılamasa da, aşağıda artıları ve eksileri vardır:
Scala Sayımı
Artıları: -Opsiyonla örnekleme veya doğrudan doğru kabul etme işlevleri (kalıcı bir mağazadan yükleme yaparken daha kolay) -Mümkün olan tüm değerler üzerinde yineleme desteklenir
Eksileri: -Ayrıntılı olmayan arama için derleme uyarısı desteklenmez (kalıp eşleşmesini daha az ideal hale getirir)
Olgu Nesneleri / Mühürlü özellikler
Artıları: -Hafalı özellikleri kullanarak, bazı değerleri önceden oluşturabilirken, diğerleri oluşturma zamanında enjekte edilebilir - desen eşleşmesi için tam destek (tanımlanan / uygulanan yöntemleri uygulayın)
Eksileri: -Sürekli bir mağazadan başlatırken - genellikle burada desen eşleşmesini kullanmanız veya tüm olası 'numaralandırma değerleri' listenizi tanımlamanız gerekir
Nihayetinde fikrimi değiştirmemi sağlayan şey, aşağıdaki pasaj gibi bir şeydi:
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
}
.get
Aramalar iğrenç idi - aşağıdaki şekilde yerine ben sadece numaralandırma üzerinde withName yöntemini çağırabilirsiniz numaralandırma kullanarak:
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)
}
}
Bu nedenle, tercihimin ileride değerlerin bir depodan ve vaka nesnelerinden / mühürlü özelliklerden erişilmesi amaçlandığında Numaralandırmaları kullanmak olduğunu düşünüyorum.
Ben tercih ederim case objects
(kişisel tercih meselesi). Bu yaklaşımın doğasında var olan problemlerle başa çıkmak için (tüm unsurlar üzerinde dize ayrıştır ve yinele), mükemmel olmayan ancak etkili olan birkaç satır ekledim.
Ben burada yararlı olabilir ve ayrıca başkalarının geliştirmek olabilir bekliyor kod yapıştırıyorum.
/**
* 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 }
}
GatesDa'nın işe nasıl cevap alacağını hala arayanlar için : Yalnızca vaka nesnesini, başlatmayı bildirdikten sonra başvurabilirsiniz:
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
}
Ben sahip olmanın en büyük avantajı düşünüyorum case classes
üzerinde enumerations
kullanabileceğiniz olmasıdır tip sınıfı modelini aka geçici poliforfizmi . Gibi numaralandırmaları eşleştirmenize gerek yok:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
onun yerine şöyle bir şey olacak:
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() = ...
}