Có hướng dẫn thực hành tốt nhất nào về thời điểm sử dụng các lớp tình huống (hoặc đối tượng trường hợp) so với việc mở rộng Bảng liệt kê trong Scala không?
Họ dường như cung cấp một số lợi ích tương tự.
enum
(cho giữa năm 2020).
Có hướng dẫn thực hành tốt nhất nào về thời điểm sử dụng các lớp tình huống (hoặc đối tượng trường hợp) so với việc mở rộng Bảng liệt kê trong Scala không?
Họ dường như cung cấp một số lợi ích tương tự.
enum
(cho giữa năm 2020).
Câu trả lời:
Một sự khác biệt lớn là nó Enumeration
đi kèm với sự hỗ trợ để khởi tạo chúng từ một số name
Chuỗi. Ví dụ:
object Currency extends Enumeration {
val GBP = Value("GBP")
val EUR = Value("EUR") //etc.
}
Sau đó, bạn có thể làm:
val ccy = Currency.withName("EUR")
Điều này hữu ích khi muốn duy trì liệt kê (ví dụ: vào cơ sở dữ liệu) hoặc tạo chúng từ dữ liệu cư trú trong các tệp. Tuy nhiên, tôi thấy nói chung rằng việc liệt kê là một chút vụng về trong Scala và có cảm giác về một tiện ích bổ sung vụng về, vì vậy bây giờ tôi có xu hướng sử dụng case object
s. A case object
linh hoạt hơn enum:
sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.
case class UnknownCurrency(name: String) extends Currency
Vì vậy, bây giờ tôi có lợi thế của ...
trade.ccy match {
case EUR =>
case UnknownCurrency(code) =>
}
Như @ chaotic3quilibrium đã chỉ ra (với một số chỉnh sửa để dễ đọc):
Về mẫu "UnknownCurrency (mã)", có nhiều cách khác để xử lý việc không tìm chuỗi mã tiền tệ hơn là "phá vỡ" bản chất đóng của
Currency
loại.UnknownCurrency
hiện thuộc loạiCurrency
có thể lẻn vào các phần khác của API.Bạn nên đẩy trường hợp đó ra bên ngoài
Enumeration
và khiến khách hàng xử lý mộtOption[Currency]
loại có thể chỉ ra rõ ràng thực sự có vấn đề phù hợp và "khuyến khích" người dùng API sắp xếp nó ra.
Để theo dõi các câu trả lời khác ở đây, nhược điểm chính của case object
s over Enumeration
s là:
Không thể lặp lại tất cả các trường hợp của "liệt kê" . Đây chắc chắn là trường hợp, nhưng tôi thấy nó cực kỳ hiếm trong thực tế rằng điều này là bắt buộc.
Không thể khởi tạo dễ dàng từ giá trị bền vững . Điều này cũng đúng nhưng, ngoại trừ trong trường hợp liệt kê rất lớn (ví dụ: tất cả các loại tiền tệ), điều này không có chi phí quá lớn.
trade.ccy
trong mẫu đặc điểm kín.
case
object
tạo ra dấu chân mã (~ 4x) lớn hơn Enumeration
? Phân biệt hữu ích đặc biệt cho scala.js
các dự án cần dấu chân nhỏ.
CẬP NHẬT: Một giải pháp dựa trên vĩ mô mới đã được tạo ra vượt trội hơn nhiều so với giải pháp tôi phác thảo dưới đây. Tôi thực sự khuyên bạn nên sử dụng giải pháp dựa trên macro mới này . Và nó xuất hiện kế hoạch cho Dotty sẽ làm cho phong cách giải pháp enum này trở thành một phần của ngôn ngữ. Ôi trời ơi!
Tóm tắt:
Có ba mẫu cơ bản để cố gắng tái tạo Java Enum
trong một dự án Scala. Hai trong ba mẫu; trực tiếp sử dụng Java Enum
và scala.Enumeration
, không có khả năng cho phép khớp mẫu đầy đủ của Scala. Và cái thứ ba; "đặc điểm kín + đối tượng trường hợp", không ... nhưng có các biến chứng khởi tạo lớp / đối tượng JVM dẫn đến việc tạo ra chỉ mục thứ tự không nhất quán.
Tôi đã tạo ra một giải pháp với hai lớp; Bảng liệt kê và bảng liệt kê Được đặt trong Gist này . Tôi đã không đăng mã vào chủ đề này vì tệp cho phép liệt kê khá lớn (+400 dòng - chứa rất nhiều ý kiến giải thích bối cảnh thực hiện).
Chi tiết:
Câu hỏi bạn đặt ra khá chung chung; "... Khi nào nên sử dụng case
các lớpobjects
so với mở rộng [scala.]Enumeration
". Và hóa ra có NHIỀU câu trả lời có thể có, mỗi câu trả lời tùy thuộc vào sự tinh tế của các yêu cầu dự án cụ thể mà bạn có. Câu trả lời có thể được giảm xuống thành ba mẫu cơ bản.
Để bắt đầu, hãy chắc chắn rằng chúng ta đang làm việc từ cùng một ý tưởng cơ bản về việc liệt kê là gì. Chúng ta hãy định nghĩa một phép liệt kê chủ yếu theo các điều khoản Enum
được cung cấp kể từ Java 5 (1.5) :
Enum
, thật tuyệt khi có thể tận dụng một cách rõ ràng việc tận dụng mô hình phù hợp với mô hình của Scala để kiểm tra mức độ liệt kê Tiếp theo, chúng ta hãy xem xét các phiên bản rút gọn của ba mẫu giải pháp phổ biến nhất được đăng:
A) Thực tế trực tiếp sử dụng mẫu JavaEnum
(trong một dự án Scala / Java hỗn hợp):
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;
}
}
Các mục sau đây từ định nghĩa liệt kê không có sẵn:
Đối với các dự án hiện tại của tôi, tôi không có lợi ích khi chấp nhận rủi ro xung quanh lộ trình dự án hỗn hợp Scala / Java. Và ngay cả khi tôi có thể chọn thực hiện một dự án hỗn hợp, mục 7 rất quan trọng để cho phép tôi nắm bắt các vấn đề về thời gian biên dịch nếu / khi tôi thêm / xóa thành viên liệt kê hoặc viết một số mã mới để xử lý các thành viên liệt kê hiện có.
B) Sử dụng mẫu " 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}
}
Các mục sau đây từ định nghĩa liệt kê không có sẵn:
Người ta cho rằng nó thực sự đáp ứng các mục định nghĩa liệt kê 5 và 6. Đối với 5, đây là một sự căng thẳng để khẳng định nó hiệu quả. Đối với 6, thật không dễ dàng để mở rộng để chứa thêm dữ liệu đơn lẻ liên quan.
C) Sử dụng scala.Enumeration
mẫu (lấy cảm hứng từ câu trả lời StackOverflow này ):
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]
}
Các mục sau đây từ định nghĩa liệt kê không có sẵn (tình cờ giống hệt với danh sách để sử dụng trực tiếp Enum Java):
Một lần nữa cho các dự án hiện tại của tôi, mục 7 rất quan trọng trong việc cho phép tôi nắm bắt các vấn đề về thời gian biên dịch nếu / khi tôi thêm / xóa thành viên liệt kê hoặc đang viết một số mã mới để xử lý các thành viên liệt kê hiện có.
Vì vậy, với định nghĩa trên về liệt kê, không có giải pháp nào trong ba giải pháp trên hoạt động vì chúng không cung cấp mọi thứ được nêu trong định nghĩa liệt kê ở trên:
Mỗi giải pháp này cuối cùng có thể được làm lại / mở rộng / tái cấu trúc để cố gắng đáp ứng một số yêu cầu còn thiếu của mỗi người. Tuy nhiên, cả Java Enum
và các scala.Enumeration
giải pháp đều không thể được mở rộng đủ để cung cấp mục 7. Và đối với các dự án của riêng tôi, đây là một trong những giá trị hấp dẫn hơn khi sử dụng loại đóng trong Scala. Tôi đặc biệt thích các cảnh báo / lỗi thời gian biên dịch để chỉ ra rằng tôi có một khoảng trống / vấn đề trong mã của mình thay vì phải lượm lặt nó ra khỏi một ngoại lệ / lỗi thời gian chạy sản xuất.
Về vấn đề đó, tôi bắt đầu làm việc với case object
con đường để xem liệu tôi có thể tạo ra một giải pháp bao gồm tất cả các định nghĩa liệt kê ở trên không. Thử thách đầu tiên là vượt qua cốt lõi của vấn đề khởi tạo đối tượng lớp / đối tượng JVM (được đề cập chi tiết trong bài đăng StackOverflow này ). Và cuối cùng tôi đã có thể tìm ra một giải pháp.
Như giải pháp của tôi là hai đặc điểm; Enumutions và EnumutionsDecorated , và vì Enumeration
đặc điểm này dài hơn 400 dòng (rất nhiều ý kiến giải thích bối cảnh), tôi đã gửi nó vào chủ đề này (điều này sẽ khiến nó kéo dài xuống trang một cách cân nhắc). Để biết chi tiết, xin vui lòng nhảy trực tiếp đến Gist .
Đây là những gì giải pháp kết thúc giống như sử dụng cùng một ý tưởng dữ liệu như trên (phiên bản nhận xét đầy đủ có sẵn ở đây ) và được triển khai trong 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
}
Đây là một ví dụ sử dụng một cặp tính trạng liệt kê mới mà tôi đã tạo (nằm trong Gist này ) để thực hiện tất cả các khả năng mong muốn và được nêu trong định nghĩa liệt kê.
Một mối quan tâm được thể hiện là tên thành viên liệt kê phải được lặp lại ( decorationOrderedSet
trong ví dụ trên). Mặc dù tôi đã giảm thiểu nó xuống một lần lặp lại, tôi không thể thấy làm thế nào để làm cho nó ít hơn do hai vấn đề:
getClass.getDeclaredClasses
có một thứ tự không xác định (và rất khó có thể theo cùng thứ tự với các case object
khai báo trong mã nguồn)Do hai vấn đề này, tôi đã phải từ bỏ việc cố gắng tạo ra một thứ tự ngụ ý và phải yêu cầu rõ ràng khách hàng xác định và khai báo nó với một số khái niệm tập hợp được đặt hàng. Vì các bộ sưu tập Scala không có triển khai cài đặt theo thứ tự chèn, điều tốt nhất tôi có thể làm là sử dụng List
kiểm tra thời gian chạy và kiểm tra xem nó có thực sự là một tập hợp không. Đó không phải là cách tôi muốn đạt được điều này.
Và đưa ra thiết kế yêu cầu danh sách / bộ thứ tự thứ hai này val
, đưa ra ChessPiecesEnhancedDecorated
ví dụ ở trên, có thể thêm case object PAWN2 extends Member
và sau đó quên thêm Decoration(PAWN2,'P2', 2)
vào decorationOrderedSet
. Vì vậy, có một kiểm tra thời gian chạy để xác minh rằng danh sách không chỉ là một tập hợp, mà còn chứa TẤT CẢ các đối tượng trường hợp mở rộng sealed trait Member
. Đó là một hình thức đặc biệt của địa ngục phản chiếu / vĩ mô để vượt qua.
Vui lòng để lại ý kiến và / hoặc phản hồi về Gist .
org.scalaolio.util.Enumeration
và org.scalaolio.util.EnumerationDecorated
: scalaolio.org
Các đối tượng trường hợp đã trả lại tên của chúng cho các phương thức toString của chúng, vì vậy việc chuyển nó thành riêng là không cần thiết. Đây là một phiên bản tương tự như jho's (phương pháp tiện lợi được bỏ qua cho ngắn gọn):
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)
}
Đối tượng lười biếng; bằng cách sử dụng vals thay vì chúng ta có thể bỏ danh sách nhưng phải lặp lại tên:
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") {}
}
Nếu bạn không bận tâm đến việc gian lận, bạn có thể tải trước các giá trị liệt kê của mình bằng API phản chiếu hoặc một cái gì đó như Google Reflections. Các đối tượng trường hợp không lười biếng cung cấp cho bạn cú pháp rõ ràng nhất:
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
}
Đẹp và sạch sẽ, với tất cả các lợi thế của các lớp tình huống và bảng liệt kê Java. Cá nhân, tôi xác định các giá trị liệt kê bên ngoài đối tượng để phù hợp hơn với mã Scala thành ngữ:
object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency
Currency.values
, tôi chỉ nhận lại các giá trị mà tôi đã truy cập trước đó. Có đường nào quanh đó không?
Ưu điểm của việc sử dụng các lớp trường hợp so với Số liệu là:
Ưu điểm của việc sử dụng Số liệt kê thay vì các lớp trường hợp là:
Vì vậy, nói chung, nếu bạn chỉ cần một danh sách các hằng số đơn giản theo tên, hãy sử dụng bảng liệt kê. Mặt khác, nếu bạn cần một cái gì đó phức tạp hơn một chút hoặc muốn sự an toàn bổ sung của trình biên dịch cho bạn biết nếu bạn có tất cả các kết quả khớp được chỉ định, hãy sử dụng các lớp tình huống.
CẬP NHẬT: Mã dưới đây có một lỗi, được mô tả ở đây . Chương trình thử nghiệm bên dưới hoạt động, nhưng nếu bạn sử dụng DayOfWeek.Mon (ví dụ) trước DayOfWeek, thì nó sẽ thất bại vì DayOfWeek chưa được khởi tạo (sử dụng một đối tượng bên trong không khiến đối tượng bên ngoài được khởi tạo). Bạn vẫn có thể sử dụng mã này nếu bạn làm một cái gì đó giống như val enums = Seq( DayOfWeek )
trong lớp chính của mình, buộc khởi tạo enum của bạn hoặc bạn có thể sử dụng các sửa đổi của chaotic3quilibrium. Mong chờ một enum dựa trên vĩ mô!
Nếu bạn muốn
sau đó có thể được quan tâm. Phản hồi chào mừng.
Trong triển khai này có các lớp cơ sở Enum và EnumVal trừu tượng, mà bạn mở rộng. Chúng ta sẽ thấy các lớp đó trong một phút, nhưng trước tiên, đây là cách bạn sẽ xác định một 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()
}
Lưu ý rằng bạn phải sử dụng từng giá trị enum (gọi phương thức áp dụng của nó) để đưa nó vào cuộc sống. [Tôi muốn các đối tượng bên trong không lười biếng trừ khi tôi đặc biệt yêu cầu chúng. Tôi nghĩ.]
Tất nhiên chúng ta có thể thêm các phương thức / dữ liệu vào DayOfWeek, Val hoặc các đối tượng trường hợp riêng lẻ nếu chúng ta muốn.
Và đây là cách bạn sẽ sử dụng một enum như vậy:
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"
}
}
Đây là những gì bạn nhận được khi biên dịch nó:
DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination Sat
missing combination Sun
def aufDeutsch( day: DayOfWeek.Val ) = day match {
^
one warning found
Bạn có thể thay thế "khớp ngày" bằng "khớp (ngày: @unchecked)" trong trường hợp bạn không muốn cảnh báo như vậy hoặc đơn giản chỉ bao gồm trường hợp bắt tất cả vào cuối.
Khi bạn chạy chương trình trên, bạn nhận được kết quả đầu ra này:
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
Lưu ý rằng vì Danh sách và Bản đồ là bất biến, bạn có thể dễ dàng loại bỏ các yếu tố để tạo các tập hợp con, mà không phá vỡ enum.
Đây là lớp Enum (và EnumVal bên trong nó):
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 )
}
}
}
Và đây là một cách sử dụng nâng cao hơn của nó để điều khiển ID và thêm dữ liệu / phương thức vào bản tóm tắt Val và cho chính enum:
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
] là một tội lỗi chết người trong thế giới FP", tôi không nghĩ rằng ý kiến đó được chấp nhận rộng rãi.
Tôi có một lib đơn giản đẹp ở đây cho phép bạn sử dụng các đặc điểm / lớp niêm phong làm giá trị enum mà không phải duy trì danh sách giá trị của riêng bạn. Nó dựa vào một macro đơn giản không phụ thuộc vào lỗi knownDirectSubclasses
.
Cập nhật tháng 3 năm 2017: như nhận xét của Anthony Accioly , scala.Enumeration/enum
PR đã bị đóng cửa.
Dotty (trình biên dịch thế hệ tiếp theo cho Scala) sẽ dẫn đầu, mặc dù vấn đề dotty 1970 và PR 1958 của Martin Oderky .
Lưu ý: hiện tại (tháng 8 năm 2016, hơn 6 năm sau) đề xuất xóa scala.Enumeration
: PR 5352
Khấu hao
scala.Enumeration
, thêm@enum
chú thíchCú pháp
@enum
class Toggle {
ON
OFF
}
là một ví dụ triển khai có thể, ý định là cũng hỗ trợ các ADT tuân thủ các hạn chế nhất định (không có các tham số lồng nhau, đệ quy hoặc thay đổi khác nhau), ví dụ:
@enum
sealed trait Toggle
case object ON extends Toggle
case object OFF extends Toggle
Khấu hao những thảm họa không được thừa nhận đó là
scala.Enumeration
.Ưu điểm của @enum so với scala.Enumutions:
- Thực tế hoạt động
- Java interop
- Không có vấn đề tẩy
- Không có DSL nhỏ khó hiểu khi tìm hiểu cách liệt kê
Nhược điểm: Không có.
Điều này giải quyết vấn đề không thể có một cơ sở mã hỗ trợ Scala-JVM
Scala.js
và Scala-Native (mã nguồn Java không được hỗ trợScala.js/Scala-Native
, mã nguồn Scala không thể xác định các enum được API hiện có trên Scala-JVM chấp nhận).
Một nhược điểm khác của các lớp trường hợp so với Số liệt kê khi bạn sẽ cần lặp lại hoặc lọc qua tất cả các trường hợp. Đây là một khả năng liệt kê tích hợp (và cả enum Java) trong khi các lớp trường hợp không tự động hỗ trợ khả năng đó.
Nói cách khác: "không có cách nào dễ dàng để có được danh sách tổng số các giá trị được liệt kê với các lớp trường hợp".
Nếu bạn nghiêm túc về việc duy trì khả năng tương tác với các ngôn ngữ JVM khác (ví dụ Java) thì tùy chọn tốt nhất là viết các enum Java. Chúng hoạt động trong suốt từ cả mã Scala và Java, nhiều hơn có thể nói cho scala.Enumeration
các đối tượng hoặc trường hợp. Chúng ta đừng có thư viện liệt kê mới cho mọi dự án sở thích mới trên GitHub, nếu có thể tránh được!
Tôi đã thấy các phiên bản khác nhau của việc tạo ra một trường hợp bắt chước một bảng liệt kê. Đây là phiên bản của tôi:
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)
}
}
Điều này cho phép bạn xây dựng các lớp trường hợp trông như sau:
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)
}
Có lẽ ai đó có thể đưa ra một mẹo hay hơn là chỉ cần thêm một lớp trường hợp vào danh sách như tôi đã làm. Đây là tất cả những gì tôi có thể nghĩ ra vào lúc đó.
Tôi đã qua lại hai lựa chọn này trong vài lần gần đây tôi cần chúng. Cho đến gần đây, sở thích của tôi là dành cho tùy chọn đối tượng đặc điểm / trường hợp được niêm phong.
1) Tuyên bố liệt kê Scala
object OutboundMarketMakerEntryPointType extends Enumeration {
type OutboundMarketMakerEntryPointType = Value
val Alpha, Beta = Value
}
2) Đặc điểm kín + Đối tượng trường hợp
sealed trait OutboundMarketMakerEntryPointType
case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType
case object BetaEntryPoint extends OutboundMarketMakerEntryPointType
Mặc dù cả hai điều này không thực sự đáp ứng tất cả những gì một bảng liệt kê java mang lại cho bạn, dưới đây là những ưu và nhược điểm:
Bảng liệt kê Scala
Ưu điểm: -Các liên kết để khởi tạo với tùy chọn hoặc trực tiếp giả định chính xác (dễ dàng hơn khi tải từ cửa hàng liên tục) - Hỗ trợ trên tất cả các giá trị có thể được hỗ trợ
Nhược điểm: Không hỗ trợ cảnh báo biên dịch cho tìm kiếm không toàn diện (làm cho kết hợp mẫu không lý tưởng hơn)
Đối tượng trường hợp / Đặc điểm niêm phong
Ưu điểm: -Sử dụng các đặc điểm được niêm phong, chúng tôi có thể khởi tạo trước một số giá trị trong khi các giá trị khác có thể được đưa vào tại thời điểm tạo - hỗ trợ đầy đủ cho khớp mẫu (phương pháp áp dụng / không phù hợp được xác định)
Nhược điểm: -Đánh giá từ một cửa hàng liên tục - bạn thường phải sử dụng khớp mẫu ở đây hoặc xác định danh sách của riêng bạn về tất cả các 'giá trị enum' có thể
Điều cuối cùng khiến tôi thay đổi ý kiến của mình là một đoạn như đoạn trích sau:
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
}
Các .get
cuộc gọi thật gớm ghiếc - sử dụng phép liệt kê thay vào đó tôi chỉ có thể gọi phương thức withName trên bảng liệt kê như sau:
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)
}
}
Vì vậy, tôi nghĩ rằng sở thích của tôi sẽ tiếp tục là sử dụng Bảng liệt kê khi các giá trị được dự định truy cập từ kho lưu trữ và các đối tượng trường hợp / các đặc điểm được niêm phong khác.
Tôi thích case objects
(đó là vấn đề sở thích cá nhân). Để đối phó với các vấn đề cố hữu đối với phương pháp đó (chuỗi phân tích cú pháp và lặp lại trên tất cả các yếu tố), tôi đã thêm một vài dòng không hoàn hảo, nhưng hiệu quả.
Tôi đang dán cho bạn mã ở đây với hy vọng nó có thể hữu ích và những người khác cũng có thể cải thiện nó.
/**
* 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 }
}
Đối với những người vẫn đang tìm cách làm cho câu trả lời của GatesDa hoạt động : Bạn chỉ có thể tham chiếu đối tượng trường hợp sau khi khai báo để khởi tạo nó:
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
}
Tôi nghĩ rằng lợi thế lớn nhất của việc có case classes
hơn enumerations
là bạn có thể sử dụng kiểu lớp hay còn gọi là đa hình ad-hoc . Không cần phải ghép các enum như:
someEnum match {
ENUMA => makeThis()
ENUMB => makeThat()
}
thay vào đó bạn sẽ có một cái gì đó như:
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() = ...
}