วัตถุเคสเทียบกับการแจกแจงใน Scala


231

มีแนวทางปฏิบัติที่ดีที่สุดเกี่ยวกับเวลาใดที่จะใช้คลาสเคส (หรืออ็อบเจ็กต์เคส) เทียบกับการขยายการแจงนับใน Scala?

พวกเขาดูเหมือนจะให้ประโยชน์เหมือนกัน


2
ฉันได้เขียนภาพรวมเล็ก ๆ เกี่ยวกับ scala การแจงนับและทางเลือกคุณอาจพบว่ามีประโยชน์: pedrorijo.com/blog/scala-enums/
pedrorijo91

1
ดูเพิ่มเติมที่สกาล่า 3 ที่ใช้ Dottyenum (สำหรับกลางปี ​​2563)
VonC

คำตอบ:


223

ความแตกต่างที่สำคัญอย่างหนึ่งคือEnumerationมาพร้อมกับการสนับสนุนการยกตัวอย่างจากnameสตริง ตัวอย่างเช่น:

object Currency extends Enumeration {
   val GBP = Value("GBP")
   val EUR = Value("EUR") //etc.
} 

จากนั้นคุณสามารถทำได้:

val ccy = Currency.withName("EUR")

สิ่งนี้มีประโยชน์เมื่อต้องการยืนยันการแจกแจง (เช่นไปยังฐานข้อมูล) หรือสร้างจากข้อมูลที่อยู่ในไฟล์ อย่างไรก็ตามฉันพบโดยทั่วไปว่าการแจกแจงนั้นค่อนข้างงุ่มง่ามใน Scala และมีความรู้สึกของ add-on ที่น่าอึดอัดใจดังนั้นตอนนี้ฉันมักจะใช้case objects A case objectมีความยืดหยุ่นมากกว่า Enum:

sealed trait Currency { def name: String }
case object EUR extends Currency { val name = "EUR" } //etc.

case class UnknownCurrency(name: String) extends Currency

ดังนั้นตอนนี้ฉันได้เปรียบ ...

trade.ccy match {
  case EUR                   =>
  case UnknownCurrency(code) =>
}

ในฐานะที่เป็น@ chaotic3quilibriumชี้ให้เห็น (มีการแก้ไขบางอย่างเพื่อความสะดวกในการอ่าน):

เกี่ยวกับรูปแบบ "UnknownCurrency (รหัส)" มีวิธีอื่นในการจัดการไม่ค้นหาสตริงรหัสสกุลเงินมากกว่า "ทำลาย" ลักษณะชุดที่ปิดของCurrencyประเภท UnknownCurrencyการเป็นประเภทCurrencyสามารถแอบเข้าไปในส่วนอื่น ๆ ของ API ได้แล้ว

ขอแนะนำให้ผลักกรณีนี้ออกไปข้างนอกEnumerationและทำให้ลูกค้าจัดการกับOption[Currency]ประเภทที่ชัดเจนว่ามีปัญหาการจับคู่และ "กระตุ้น" ผู้ใช้ API ให้เรียงลำดับตัวเขาเอง

หากต้องการติดตามคำตอบอื่น ๆ ที่นี่ข้อเสียเปรียบหลักของcase objects เกินEnumerationคือ:

  1. ไม่สามารถย้ำกว่าทุกกรณีของ "การแจงนับ" เป็นกรณีนี้ แต่ฉันพบว่ามันยากมากในทางปฏิบัติที่จำเป็น

  2. ไม่สามารถยกตัวอย่างได้อย่างง่ายดายจากค่าที่เก็บไว้ สิ่งนี้ก็เป็นจริงเช่นกันยกเว้นในกรณีที่มีการแจกแจงจำนวนมาก (ตัวอย่างเช่นทุกสกุลเงิน) สิ่งนี้ไม่ได้มีค่าใช้จ่ายมาก


10
ข้อแตกต่างอื่น ๆ คือ Enumeration enum ถูกสั่งให้ออกจากกล่องในขณะที่กรณีของวัตถุที่ใช้ enum ไม่ได้เป็นไปตามนั้น
om-nom-nom

1
อีกจุดสำหรับวัตถุกรณีคือถ้าคุณสนใจเกี่ยวกับการทำงานร่วมกันของจาวา การแจงนับจะส่งคืนค่าเป็น Enumeration.Value ดังนั้น 1) ต้องการ scala-library, 2) การสูญเสียข้อมูลชนิดที่แท้จริง
juanmirocks

7
@oxbow_lakes เกี่ยวกับประเด็นที่ 1 โดยเฉพาะในส่วนนี้ "... ฉันพบว่ามันยากมากในทางปฏิบัติที่จำเป็นต้องมี": เห็นได้ชัดว่าคุณไม่ค่อยทำงาน UI มากนัก นี่เป็นกรณีการใช้งานที่ธรรมดามาก การแสดงรายการ (ดรอปดาวน์) ของสมาชิกการแจงนับที่ถูกต้องที่จะเลือก
chaotic3quilibrium

ฉันไม่เข้าใจประเภทของรายการที่จับคู่trade.ccyในลักษณะตัวอย่างที่ปิดสนิท
rloth

และไม่case objectสร้างรอยเท้าโค้ดขนาดใหญ่กว่า (~ 4x) มากกว่าEnumeration? ความแตกต่างที่มีประโยชน์โดยเฉพาะอย่างยิ่งสำหรับscala.jsโครงการที่ต้องการรอยขนาดเล็ก
ecoe

69

อัปเดต:มีการสร้างโซลูชันที่ใช้มาโคร ใหม่ซึ่งเหนือกว่าโซลูชันที่ฉันเขียนไว้ด้านล่าง ผมขอแนะนำให้ใช้ใหม่นี้การแก้ปัญหาตามมาโคร และดูเหมือนว่าแผนสำหรับ Dotty จะทำให้รูปแบบการแก้ปัญหา Enum นี้เป็นส่วนหนึ่งของภาษา Whoohoo!

สรุป:
มีสามรูปแบบพื้นฐานสำหรับการพยายามสร้าง Java Enumภายในโครงการ Scala สองในสามรูปแบบ; การใช้ Java โดยตรงEnumและscala.Enumerationไม่สามารถเปิดใช้งานการจับคู่รูปแบบที่ครบถ้วนสมบูรณ์ของ Scala ได้ และอันที่สาม "ผนึกลักษณะ + กรณีวัตถุ" ไม่ ... แต่มีความซับซ้อนของการเริ่มต้นคลาส / วัตถุ JVMส่งผลให้เกิดการสร้างดัชนีลำดับไม่สอดคล้องกัน

ฉันสร้างโซลูชันที่มีสองคลาส การแจงนับและการแจงนับตกแต่งอยู่ในส่วนสำคัญนี้ ฉันไม่ได้โพสต์รหัสลงในกระทู้นี้เนื่องจากไฟล์สำหรับการแจงนับค่อนข้างใหญ่ (+400 บรรทัด - มีความคิดเห็นมากมายที่อธิบายถึงบริบทการใช้งาน)

รายละเอียด:
คำถามที่คุณถามนั้นค่อนข้างธรรมดา "... เมื่อใดควรใช้caseคลาสobjectsกับการขยาย[scala.]Enumeration" และปรากฎว่ามีคำตอบที่เป็นไปได้มากมายแต่ละคำตอบขึ้นอยู่กับรายละเอียดปลีกย่อยของข้อกำหนดโครงการเฉพาะที่คุณมี คำตอบสามารถลดลงได้ถึงสามรูปแบบพื้นฐาน

ในการเริ่มต้นให้ตรวจสอบให้แน่ใจว่าเรากำลังทำงานจากแนวคิดพื้นฐานเดียวกันว่าการแจงนับคืออะไร ลองกำหนดการแจงนับเป็นส่วนใหญ่ในแง่ของการEnumให้เป็นของ Java 5 (1.5) :

  1. มันมีชุดปิดสมาชิกตามธรรมชาติ
    1. มีจำนวนสมาชิกคงที่
    2. สมาชิกจะได้รับคำสั่งตามธรรมชาติและจัดทำดัชนีอย่างชัดเจน
      • ซึ่งแตกต่างจากการจัดเรียงตามเกณฑ์ที่ยอมรับได้ของสมาชิกบางคน
    3. สมาชิกแต่ละคนมีชื่อเฉพาะภายในชุดรวมของสมาชิกทั้งหมด
  2. สมาชิกทุกคนสามารถทำซ้ำได้อย่างง่ายดายผ่านดัชนีของพวกเขา
  3. สมาชิกสามารถดึงข้อมูลได้ด้วยชื่อ (case sensitive)
    1. มันจะค่อนข้างดีถ้าสมาชิกสามารถเรียกดูด้วยชื่อที่ไม่ตรงตามตัวพิมพ์ใหญ่
  4. สมาชิกสามารถดึงข้อมูลได้ด้วยดัชนี
  5. สมาชิกอาจใช้การทำให้เป็นอนุกรมได้อย่างง่ายดายโปร่งใสและมีประสิทธิภาพ
  6. สมาชิกอาจขยายได้อย่างง่ายดายเพื่อเก็บข้อมูล singleton-ness เพิ่มเติมที่เกี่ยวข้อง
  7. เมื่อนึกถึง Java Enumแล้วละก็คงเป็นการดีที่จะได้ใช้ประโยชน์จากรูปแบบการจับคู่แบบหมดจดของ Scala เพื่อตรวจสอบการแจงนับ

ต่อไปเรามาดูรูปแบบการแก้ปัญหาที่พบบ่อยทั้งสามแบบที่โพสต์:

A)จริงๆแล้วใช้รูปแบบJavaEnumโดยตรง(ในโครงการ Scala / Java แบบผสม):

public enum ChessPiece {
    KING('K', 0)
  , QUEEN('Q', 9)
  , BISHOP('B', 3)
  , KNIGHT('N', 3)
  , ROOK('R', 5)
  , PAWN('P', 1)
  ;

  private char character;
  private int pointValue;

  private ChessPiece(char character, int pointValue) {
    this.character = character; 
    this.pointValue = pointValue;   
  }

  public int getCharacter() {
    return character;
  }

  public int getPointValue() {
    return pointValue;
  }
}

ไอเท็มต่อไปนี้จากนิยามการแจงนับไม่พร้อมใช้งาน:

  1. 3.1 - มันจะค่อนข้างดีถ้าสมาชิกสามารถเรียกดูด้วยชื่อที่ไม่ตรงตามตัวพิมพ์ใหญ่ - เล็ก
  2. 7 - คิดนอกเหนือจาก Enum ของ Java มันจะเป็นการดีที่จะสามารถใช้ประโยชน์จากการตรวจสอบรูปแบบการจับคู่หมดแรงของ Scala สำหรับการแจงนับอย่างชัดเจน

สำหรับโครงการปัจจุบันของฉันฉันไม่ได้รับประโยชน์จากการรับความเสี่ยงรอบทางเดินของโครงการผสม Scala / Java และถึงแม้ว่าฉันสามารถเลือกที่จะทำโครงการที่หลากหลายรายการที่ 7 เป็นสิ่งสำคัญสำหรับฉันที่จะรวบรวมปัญหาเวลารวบรวมเมื่อ / เมื่อฉันเพิ่ม / ลบสมาชิกการแจงนับหรือกำลังเขียนรหัสใหม่เพื่อจัดการกับสมาชิกการแจงนับที่มีอยู่


B)ใช้รูปแบบ " 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}
}

ไอเท็มต่อไปนี้จากนิยามการแจงนับไม่พร้อมใช้งาน:

  1. 1.2 - สมาชิกได้รับคำสั่งตามธรรมชาติและจัดทำดัชนีอย่างชัดเจน
  2. 2 - สมาชิกทุกคนสามารถทำซ้ำได้อย่างง่ายดายผ่านดัชนีของพวกเขา
  3. 3 - สมาชิกสามารถดึงข้อมูลได้ด้วยชื่อ (case sensitive)
  4. 3.1 - มันจะค่อนข้างดีถ้าสมาชิกสามารถเรียกดูด้วยชื่อที่ไม่ตรงตามตัวพิมพ์ใหญ่ - เล็ก
  5. 4 - สมาชิกสามารถดึงข้อมูลได้ด้วยดัชนี

สามารถพิสูจน์ได้ว่าเป็นไปตามรายการคำนิยามการแจกแจง 5 และ 6 สำหรับ 5 เป็นการยืดเวลาในการอ้างสิทธิ์ได้อย่างมีประสิทธิภาพ สำหรับ 6 ไม่ใช่เรื่องง่ายที่จะขยายเพื่อเก็บข้อมูล singleton-ness เพิ่มเติมที่เกี่ยวข้อง


C)การใช้scala.Enumerationรูปแบบ (ได้รับแรงบันดาลใจจากคำตอบ 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]
}

ไอเท็มต่อไปนี้จากนิยามการแจงนับไม่พร้อมใช้งาน (เกิดขึ้นเหมือนกับรายการสำหรับการใช้ Java Enum โดยตรง):

  1. 3.1 - มันจะค่อนข้างดีถ้าสมาชิกสามารถเรียกดูด้วยชื่อที่ไม่ตรงตามตัวพิมพ์ใหญ่ - เล็ก
  2. 7 - คิดนอกเหนือจาก Enum ของ Java มันจะเป็นการดีที่จะสามารถใช้ประโยชน์จากการตรวจสอบรูปแบบการจับคู่หมดแรงของ Scala สำหรับการแจงนับอย่างชัดเจน

อีกครั้งสำหรับโครงการปัจจุบันของฉันรายการที่ 7 เป็นสิ่งสำคัญที่ทำให้ฉันสามารถรวบรวมปัญหาเวลารวบรวมหาก / เมื่อฉันเพิ่ม / ลบสมาชิกการแจงนับหรือกำลังเขียนรหัสใหม่เพื่อจัดการกับสมาชิกการแจงนับที่มีอยู่


ดังนั้นตามคำจำกัดความด้านบนของการแจงนับไม่มีวิธีแก้ปัญหาสามข้อใดทำงานได้เนื่องจากไม่ได้ให้ทุกสิ่งที่ระบุไว้ในคำนิยามการแจงนับด้านบน:

  1. Java Enum โดยตรงในโครงการ Scala / Java แบบผสม
  2. "ปิดผนึกลักษณะ + กรณีวัตถุ"
  3. scala.Enumeration

ในที่สุดโซลูชันเหล่านี้แต่ละเครื่องสามารถนำกลับมาทำใหม่ / ขยาย / ปรับโครงสร้างใหม่เพื่อพยายามครอบคลุมความต้องการที่ขาดหายไปของแต่ละคน อย่างไรก็ตามทั้ง Java Enumและscala.Enumerationโซลูชันไม่สามารถขยายได้อย่างเพียงพอเพื่อให้รายการที่ 7 และสำหรับโครงการของฉันเองนี่เป็นหนึ่งในค่าที่น่าสนใจของการใช้แบบปิดใน Scala ฉันขอแนะนำให้รวบรวมคำเตือน / ข้อผิดพลาดในการรวบรวมเวลาเพื่อระบุว่าฉันมีช่องว่าง / ปัญหาในรหัสของฉันซึ่งต่างจากการรวบรวมจากข้อยกเว้น / ความล้มเหลวรันไทม์สำหรับการผลิต


ในเรื่องนั้นฉันตั้งใจจะทำงานร่วมกับcase objectทางเดินเพื่อดูว่าฉันจะสามารถสร้างโซลูชันที่ครอบคลุมคำจำกัดความการแจงนับทั้งหมดข้างต้นได้หรือไม่ ความท้าทายแรกคือการผลักดันผ่านแกนหลักของปัญหาการเริ่มต้นคลาส / วัตถุ JVM (ครอบคลุมรายละเอียดในโพสต์ StackOverflow นี้ ) และในที่สุดฉันก็สามารถหาคำตอบได้

ในฐานะที่เป็นวิธีการแก้ปัญหาของฉันเป็นสองลักษณะ; EnumerationและEnumerationDecoratedและเนื่องจากEnumerationคุณลักษณะมีความยาวมากกว่า 400 บรรทัด (ความคิดเห็นจำนวนมากที่อธิบายบริบท) ฉันกำลังดำเนินการวางลงในเธรดนี้ สำหรับรายละเอียดโปรดกระโดดโดยตรงกับการสรุปสาระสำคัญ

นี่คือสิ่งที่แก้ปัญหาลงท้ายมองเช่นการใช้ความคิดข้อมูลเดียวกันกับข้างต้น (อย่างเต็มที่แสดงความคิดเห็นรุ่นสามารถใช้ได้ที่นี่ ) 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
}

นี่คือตัวอย่างการใช้งานของคู่การแจงนับคุณลักษณะใหม่ที่ฉันสร้าง (อยู่ในGist นี้ ) เพื่อใช้ความสามารถทั้งหมดที่ต้องการและสรุปไว้ในคำจำกัดความการแจงนับ

สิ่งที่กังวลอย่างหนึ่งคือชื่อสมาชิกต้องระบุซ้ำ ( decorationOrderedSetในตัวอย่างด้านบน) ในขณะที่ฉันย่อให้เหลือน้อยที่สุดเพื่อทำซ้ำฉันไม่สามารถดูได้ว่าจะทำให้น้อยลงเนื่องจากปัญหาสองประการ:

  1. การกำหนดค่าเริ่มต้นอ็อบเจ็กต์ / คลาส JVM สำหรับโมเดลอ็อบเจ็กต์ / เคสนี้โดยเฉพาะนั้นไม่ได้ถูกกำหนดไว้ (ดูเธรด Stackoverflow นี้ )
  2. เนื้อหาที่ส่งคืนจากเมธอดgetClass.getDeclaredClassesมีลำดับที่ไม่ได้กำหนด (และค่อนข้างไม่น่าจะอยู่ในลำดับเดียวกันกับการcase objectประกาศในซอร์สโค้ด)

เมื่อพิจารณาถึงปัญหาทั้งสองนี้ฉันต้องพยายามพยายามสร้างคำสั่งโดยนัยและจำเป็นต้องให้ลูกค้ากำหนดและประกาศอย่างชัดเจนด้วยแนวคิดเกี่ยวกับชุดคำสั่งบางประเภท เนื่องจากคอลเลกชัน Scala ไม่มีการใช้ชุดคำสั่งแทรกสิ่งที่ดีที่สุดที่ฉันทำได้คือใช้Listและจากนั้นตรวจสอบรันไทม์ว่าเป็นชุดอย่างแท้จริง ไม่ใช่วิธีที่ฉันต้องการที่จะได้รับสิ่งนี้

และได้รับการออกแบบที่จำเป็นนี้รายการที่สอง / ชุดการสั่งซื้อvalที่ได้รับChessPiecesEnhancedDecoratedตัวอย่างข้างต้นมันเป็นไปได้ที่จะเพิ่มcase object PAWN2 extends Memberแล้วลืมที่จะเพิ่มการDecoration(PAWN2,'P2', 2) decorationOrderedSetจึงมีการตรวจสอบรันไทม์เพื่อตรวจสอบว่ารายการไม่ได้เป็นเพียงชุด sealed trait Memberแต่มีทั้งหมดของกรณีที่วัตถุที่ขยาย นั่นเป็นรูปแบบพิเศษของการสะท้อน / มาโครนรกในการทำงาน


กรุณาแสดงความคิดเห็นและ / หรือข้อเสนอแนะเกี่ยวกับการสรุปสาระสำคัญ


ตอนนี้ฉันได้เปิดตัว ScalaOlio library (GPLv3) รุ่นแรกซึ่งมีทั้งเวอร์ชั่นที่ทันสมัยorg.scalaolio.util.Enumerationและorg.scalaolio.util.EnumerationDecorated: scalaolio.org
chaotic3quilibrium

และกระโดดโดยตรงไปยังที่เก็บ ScalaOlio บน Github: github.com/chaotic3quilibrium/scala-olio
chaotic3quilibrium

5
นี่เป็นคำตอบที่มีคุณภาพและมีหลายสิ่งที่จะตอบสนอง ขอบคุณ
angabriel

1
ดูเหมือนว่า Odersky ต้องการอัพเกรด Dotty (Future Scala 3.0) ด้วย enum ดั้งเดิม Whoohoo! github.com/lampepfl/dotty/issues/1970
chaotic3quilibrium

62

อ็อบเจ็กต์เคสส่งคืนชื่อของพวกเขาสำหรับเมธอด toString ของพวกเขาดังนั้นการส่งผ่านในแยกต่างหากจึงไม่จำเป็น นี่คือรุ่นที่คล้ายกับของ jho (วิธีการอำนวยความสะดวกที่ละเว้นเพื่อความกะทัดรัด):

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)
}

วัตถุขี้เกียจ โดยใช้ vals แทนเราสามารถวางรายการ แต่ต้องทำซ้ำชื่อ:

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") {}
}

หากคุณไม่รังเกียจการโกงคุณสามารถโหลดค่าการแจงนับล่วงหน้าได้โดยใช้ API การสะท้อนกลับหรือบางอย่างเช่น Google Reflections วัตถุเคสที่ไม่ใช่สันหลังยาวให้ไวยากรณ์ที่สะอาดที่สุดแก่คุณ:

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
}

ดีและสะอาดพร้อมข้อดีทั้งหมดของคลาสเคสและการระบุ Java ส่วนตัวฉันกำหนดค่าการแจงนับนอกวัตถุเพื่อให้ตรงกับรหัสสกาล่าสำนวน:

object Currency extends Enum[Currency]
sealed trait Currency extends Currency.Value
case object EUR extends Currency
case object GBP extends Currency

3
คำถามหนึ่ง: วิธีแก้ปัญหาสุดท้ายเรียกว่า "วัตถุที่ไม่ใช่กรณีขี้เกียจ" แต่ในกรณีนี้วัตถุจะไม่ถูกโหลดจนกว่าเราจะใช้พวกเขา: ทำไมคุณเรียกวิธีแก้ปัญหานี้ว่าไม่ขี้เกียจ?
Seb Cesbron

2
@Noel คุณต้องใช้: วางเพื่อวางลำดับชั้นที่ปิดผนึกทั้งหมดลงใน REPL หากคุณทำไม่ได้บรรทัดเดียวที่มีคลาสฐาน / คุณลักษณะปิดผนึกจะนับเป็นไฟล์เดียวจะถูกปิดผนึกทันทีและไม่สามารถขยายได้ในบรรทัดถัดไป
Jürgen Strobel

2
@GatesDA เฉพาะข้อมูลโค้ดแรกของคุณเท่านั้นที่ไม่มีข้อผิดพลาด (เนื่องจากคุณต้องการให้ลูกค้าประกาศและกำหนดค่าอย่างชัดเจนโซลูชันที่สองและสามของคุณมีข้อผิดพลาดเล็กน้อยที่ฉันอธิบายไว้ในความคิดเห็นล่าสุดของฉัน .GBP โดยตรงและแรกค่ารายการจะ "ไม่เรียบร้อย") ฉันได้สำรวจโดเมนการระบุสกาล่าอย่างกว้างขวางและได้ครอบคลุมรายละเอียดในคำตอบของฉันในหัวข้อเดียวกันนี้: stackoverflow.com/a/25923651/501113
chaotic3quilibrium

1
อาจเป็นหนึ่งในข้อเสียของวิธีการนี้ (เมื่อเทียบกับ Java Enums อยู่แล้ว) คือเมื่อคุณพิมพ์ Currency <dot> ใน IDE จะไม่แสดงตัวเลือกที่ใช้ได้
Ivan Balashov

1
ตามที่ @SebCesbron กล่าวถึงวัตถุกรณีจะขี้เกียจที่นี่ ดังนั้นถ้าฉันโทรCurrency.valuesฉันจะรับค่ากลับที่ฉันได้เข้าถึงก่อนหน้านี้เท่านั้น มีวิธีใดบ้างไหม?
Sasgorilla

27

ข้อดีของการใช้คลาสเคสแทน Enumerations คือ:

  • เมื่อใช้คลาสตัวพิมพ์ที่ปิดผนึกคอมไพเลอร์ Scala สามารถบอกได้ว่าการจับคู่นั้นถูกระบุไว้อย่างสมบูรณ์หรือไม่เช่นเมื่อการจับคู่ที่เป็นไปได้ทั้งหมดถูกดำเนินการในการประกาศการจับคู่ ด้วยการแจกแจง Scala คอมไพเลอร์ไม่สามารถบอกได้
  • คลาสเคสสนับสนุนฟิลด์มากกว่าตามค่าการแจงนับซึ่งสนับสนุนชื่อและ ID โดยธรรมชาติ

ข้อดีของการใช้ Enumerations แทนคลาสของเคสคือ:

  • โดยทั่วไปการแจกแจงจะน้อยกว่าการเขียนโค้ด
  • การแจกแจงค่อนข้างง่ายต่อการเข้าใจสำหรับคนใหม่ที่สกาล่าเนื่องจากพวกเขาแพร่หลายในภาษาอื่น

โดยทั่วไปแล้วถ้าคุณต้องการรายการค่าคงที่แบบง่ายตามชื่อให้ใช้การแจกแจง มิฉะนั้นถ้าคุณต้องการบางสิ่งที่ซับซ้อนกว่านี้เล็กน้อยหรือต้องการความปลอดภัยเพิ่มเติมของคอมไพเลอร์ที่บอกคุณว่าคุณมีการแข่งขันทั้งหมดที่ระบุไว้ให้ใช้คลาสเคส


15

UPDATE: โค้ดข้างล่างนี้มีข้อผิดพลาดที่อธิบายไว้ที่นี่ โปรแกรมทดสอบด้านล่างใช้งานได้ แต่ถ้าคุณใช้ DayOfWeek.Mon (ตัวอย่าง) ก่อน DayOfWeek เองมันจะล้มเหลวเพราะ DayOfWeek ยังไม่ได้เริ่มต้น (การใช้วัตถุภายในไม่ทำให้วัตถุภายนอกถูกเริ่มต้น) คุณยังคงสามารถใช้รหัสนี้ได้หากคุณทำอะไรบางอย่างval enums = Seq( DayOfWeek )ในคลาสหลักบังคับให้เริ่มการทำงานของ enums หรือคุณสามารถใช้การดัดแปลงของ chaotic3quilibrium รอคอยที่จะ enum ตามแมโคร!


ถ้าคุณต้องการ

  • คำเตือนเกี่ยวกับการจับคู่รูปแบบที่ไม่ละเอียด
  • Int ID ที่กำหนดให้กับแต่ละค่า enum ซึ่งคุณสามารถเลือกควบคุมได้
  • List ที่ไม่เปลี่ยนรูปของค่า enum ตามลำดับที่กำหนดไว้
  • แผนที่ที่ไม่เปลี่ยนรูปจากชื่อถึงค่า enum
  • แผนที่ที่ไม่เปลี่ยนรูปจาก id ไปยัง enum value
  • place to stick เมธอด / ข้อมูลสำหรับค่า enum ทั้งหมดหรือเฉพาะหรือสำหรับ enum โดยรวม
  • สั่งค่า enum (เพื่อให้คุณสามารถทดสอบได้เช่นวัน <วันพุธหรือไม่)
  • ความสามารถในการขยาย enum หนึ่งเพื่อสร้างคนอื่น ๆ

ดังนั้นสิ่งต่อไปนี้อาจเป็นที่สนใจ ข้อเสนอแนะยินดีต้อนรับ

ในการใช้งานนี้มีคลาสฐาน Enum และ EnumVal ที่เป็นนามธรรมซึ่งคุณขยาย เราจะเห็นชั้นเรียนเหล่านั้นในนาที แต่ก่อนอื่นนี่คือวิธีที่คุณจะกำหนด 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()
}

โปรดทราบว่าคุณต้องใช้แต่ละค่า enum (เรียกใช้วิธีการใช้) เพื่อทำให้มันมีชีวิต [ฉันหวังว่าวัตถุภายในจะไม่ขี้เกียจเว้นแต่ฉันจะขอให้พวกมันเป็นพิเศษ ฉันคิด.]

แน่นอนว่าเราสามารถเพิ่มวิธีการ / ข้อมูลไปยัง DayOfWeek, Val หรือวัตถุเคสแต่ละรายการหากเราต้องการ

และนี่คือวิธีที่คุณจะใช้ Enum เช่นนี้:

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"
  }

}

นี่คือสิ่งที่คุณจะได้รับเมื่อรวบรวม:

DayOfWeekTest.scala:31: warning: match is not exhaustive!
missing combination            Sat
missing combination            Sun

  def aufDeutsch( day: DayOfWeek.Val ) = day match {
                                         ^
one warning found

คุณสามารถแทนที่ "การจับคู่วัน" ด้วยการจับคู่ "(วัน: @unchecked)" ซึ่งคุณไม่ต้องการคำเตือนดังกล่าวหรือเพียงแค่รวมกรณีจับทั้งหมดในตอนท้าย

เมื่อคุณเรียกใช้โปรแกรมข้างต้นคุณจะได้รับผลลัพธ์นี้:

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

โปรดทราบว่าเนื่องจากรายการและแผนที่ไม่เปลี่ยนรูปคุณจึงสามารถลบองค์ประกอบต่างๆเพื่อสร้างชุดย่อยได้อย่างง่ายดายโดยไม่ทำลายตัวเอง

นี่คือคลาส Enum (และ 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 )
    }
  }

}

และนี่คือการใช้งานขั้นสูงเพิ่มเติมซึ่งควบคุม ID และเพิ่มข้อมูล / วิธีการในการนามธรรม Val และเพื่อ 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)
}

Tyvm ที่ให้สิ่งนี้ ฉันซาบซึ้งจริงๆ อย่างไรก็ตามฉันสังเกตเห็นว่าใช้ "var" แทน val และนี่คือบาปแห่งความตายของเขตแดนในโลก FP ดังนั้นมีวิธีการใช้เช่นนี้ว่าไม่มีการใช้ var? แค่อยากรู้ว่านี่เป็นกรณีของประเภทขอบ FP หรือไม่และฉันไม่เข้าใจว่าการติดตั้ง FP ของคุณเป็นสิ่งที่ไม่พึงประสงค์
chaotic3quilibrium

2
ฉันอาจช่วยคุณไม่ได้ มันค่อนข้างธรรมดาใน Scala ที่จะเขียนคลาสที่เปลี่ยนแปลงภายใน แต่ไม่เปลี่ยนรูปให้กับผู้ที่ใช้มัน ในตัวอย่างข้างต้นผู้ใช้ของ DayOfWeek ไม่สามารถกลายพันธุ์ enum ได้ ตัวอย่างเช่นไม่มีทางที่จะเปลี่ยน ID ของวันอังคารหรือชื่อของมันหลังจากข้อเท็จจริง แต่ถ้าคุณต้องการการนำไปใช้ซึ่งปราศจากการกลายพันธุ์ภายในฉันก็ไม่มีอะไร แม้ว่าฉันจะไม่แปลกใจเลยที่จะเห็นสิ่งอำนวยความสะดวก enum ใหม่ที่ดีโดยอิงจากแมโครใน 2.11; แนวคิดกำลังถูกเตะใน scala-lang
AmigoNico

ฉันพบข้อผิดพลาดแปลก ๆ ใน Scala Worksheet ถ้าฉันใช้หนึ่งในอินสแตนซ์ของค่าโดยตรงฉันได้รับข้อผิดพลาดในการเริ่มต้น อย่างไรก็ตามถ้าฉันทำการเรียกใช้เมธอด. value เพื่อดูเนื้อหาของการแจงนับว่าใช้งานได้และใช้งานอินสแตนซ์ค่าโดยตรง แนวคิดใดที่ข้อผิดพลาดในการเริ่มต้นคืออะไร และวิธีที่ดีที่สุดคือการมั่นใจว่าการเริ่มต้นเกิดขึ้นในลำดับที่เหมาะสมโดยไม่คำนึงถึงการเรียกประชุม?
chaotic3quilibrium

@ chaotic3quilibrium: ว้าว! ขอบคุณสำหรับการติดตามนี้และแน่นอนว่าต้องขอบคุณ Rex Kerr สำหรับการยกของหนัก ฉันจะพูดถึงปัญหาที่นี่และอ้างถึงคำถามที่คุณสร้างขึ้น
AmigoNico

"[การใช้var] เป็นบาปของมนุษย์ที่มีแนวเขตแดนในโลก FP" - ฉันไม่คิดว่าความคิดเห็นนั้นเป็นที่ยอมรับในระดับสากล
Erik Kaplun

12

ฉันมี lib ง่าย ๆ ที่นี่ที่ให้คุณใช้ลักษณะ / คลาสที่ปิดผนึกเป็นค่า enum โดยไม่ต้องเก็บรักษารายการค่าของคุณเอง knownDirectSubclassesมันอาศัยแมโครง่ายๆที่ไม่ขึ้นอยู่กับรถ

https://github.com/lloydmeta/enumeratum


10

อัปเดตมีนาคม 2560: ตามความเห็นของAnthony Acciolyการscala.Enumeration/enumประชาสัมพันธ์ถูกปิด

บ้า ๆ บอ ๆ (คอมไพเลอร์รุ่นต่อไปสำหรับ Scala) จะนำ แต่ขี้หลงขี้ลืมปัญหา 1970และมาร์ตินโอเดอร์ สกี ของ PR 1958


หมายเหตุ: ขณะนี้มีข้อเสนอที่จะลบออกscala.Enumeration: (สิงหาคม 2559, 6 ปีขึ้นไป) : PR 5352

เลิกใช้scala.Enumerationเพิ่ม@enumคำอธิบายประกอบ

ไวยากรณ์

@enum
 class Toggle {
  ON
  OFF
 }

เป็นตัวอย่างการนำไปปฏิบัติที่เป็นไปได้ความตั้งใจที่จะสนับสนุน ADT ที่สอดคล้องกับข้อ จำกัด บางประการ (ไม่มีการซ้อนการเรียกซ้ำหรือการสร้างพารามิเตอร์คอนสตรัคเตอร์ที่แตกต่างกัน) เช่น:

@enum
sealed trait Toggle
case object ON  extends Toggle
case object OFF extends Toggle

deprecates scala.Enumerationภัยพิบัติดักดานที่เป็น

ข้อดีของ @enum มากกว่าเรื่องกาล่า

  • ใช้งานได้จริง
  • Java interop
  • ไม่มีปัญหาการลบ
  • ไม่มีสับสน mini-DSL เพื่อเรียนรู้เมื่อกำหนด enumerations

ข้อเสีย: ไม่มี

สิ่งนี้แก้ปัญหาการไม่สามารถมีหนึ่ง codebase ที่รองรับ Scala-JVM Scala.jsและ Scala-Native (ไม่สนับสนุนซอร์สโค้ด Java บนScala.js/Scala-Nativeซอร์สโค้ดของ Scala ไม่สามารถกำหนด enums ที่ APIs ที่มีอยู่บน Scala-JVM ได้รับการยอมรับ


การประชาสัมพันธ์ด้านบนปิดลง (ไม่มีความสุข) ตอนนี้เป็นปี 2017 และดูเหมือนว่า Dotty จะได้รับการสร้าง Enum ในที่สุด นี่คือปัญหาและมาร์ตินพีอาร์ ผสานรวมผสาน!
Anthony Accioly

8

ข้อเสียอีกอย่างหนึ่งของคลาสเคสเทียบกับการแจกแจงเมื่อคุณต้องทำซ้ำหรือกรองในทุกอินสแตนซ์ นี่เป็นความสามารถในตัวของ Enumeration (และ Java enums ด้วย) ในขณะที่คลาสเคสไม่สนับสนุนความสามารถดังกล่าวโดยอัตโนมัติ

กล่าวอีกนัยหนึ่ง: "ไม่มีวิธีง่ายๆในการรับรายการชุดค่ารวมทั้งหมดพร้อมคลาสเคส"


5

หากคุณจริงจังเกี่ยวกับการรักษาความสามารถในการทำงานร่วมกันกับภาษา JVM อื่น ๆ (เช่น Java) ตัวเลือกที่ดีที่สุดคือการเขียน enums Java งานเหล่านั้นโปร่งใสจากทั้ง Scala และโค้ด Java ซึ่งมากกว่าที่สามารถพูดได้สำหรับscala.Enumerationวัตถุหรือกรณี อย่ามีห้องสมุดแจกแจงใหม่สำหรับโครงการงานอดิเรกใหม่ทุกรายการใน GitHub หากสามารถหลีกเลี่ยงได้!


4

ฉันเคยเห็นรุ่นต่าง ๆ ของการทำคลาสเคสเลียนแบบการแจงนับ นี่คือรุ่นของฉัน:

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)
    }
}

ซึ่งอนุญาตให้คุณสร้างคลาสเคสที่มีลักษณะดังต่อไปนี้:

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)
}

บางทีบางคนอาจมีกลอุบายที่ดีกว่าการเพิ่มคลาสเคสแต่ละรายการลงในรายการอย่างที่ฉันทำ นี่คือทั้งหมดที่ฉันสามารถทำได้ในเวลานั้น


ทำไมสองวิธีแยกกันโดยไม่สมัครใจ
Saish

@ โจฉันพยายามหาวิธีแก้ปัญหาของคุณตามที่เป็นอยู่ แต่มันไม่ได้รวบรวม ในส่วนของโค้ดที่สองมีการอ้างอิงไปยังไซต์ใน "type V = Site" ฉันไม่แน่ใจในสิ่งที่อ้างถึงเพื่อล้างข้อผิดพลาดในการรวบรวม ถัดไปทำไมคุณถึงจัดเตรียมวงเล็บที่ว่างเปล่าสำหรับ "สกุลเงินระดับนามธรรม"? พวกเขาไม่สามารถถูกทิ้ง สุดท้ายทำไมคุณถึงใช้ var ใน "var values ​​= ... " นี่ไม่ได้หมายความว่าลูกค้าสามารถทำได้ทุกที่ในรหัสกำหนดรายการใหม่ให้กับค่าหรือไม่ มันจะดีกว่าไหมถ้าจะทำให้มันเป็น val แทนที่จะเป็น var?
chaotic3quilibrium

2

ฉันได้ย้อนกลับไปทั้งสองทางเลือกสองสามครั้งสุดท้ายที่ฉันต้องการ จนกระทั่งเมื่อเร็ว ๆ นี้การตั้งค่าของฉันมีไว้สำหรับตัวเลือกคุณลักษณะ / ตัวเลือกวัตถุที่ปิดผนึก

1) ปฏิญญาการแจงนับสกาล่า

object OutboundMarketMakerEntryPointType extends Enumeration {
  type OutboundMarketMakerEntryPointType = Value

  val Alpha, Beta = Value
}

2) คุณสมบัติปิดผนึก + กรณีวัตถุ

sealed trait OutboundMarketMakerEntryPointType

case object AlphaEntryPoint extends OutboundMarketMakerEntryPointType

case object BetaEntryPoint extends OutboundMarketMakerEntryPointType

แม้ว่าสิ่งเหล่านี้จะไม่ตรงกับสิ่งที่แจงนับให้จริงทั้งหมด แต่ด้านล่างเป็นข้อดีและข้อเสีย:

การนับสกาล่า

ข้อดี: - ฟังก์ชันสำหรับการสร้างอินสแตนซ์ด้วยตัวเลือกหรือสมมติว่าถูกต้องโดยตรง (ง่ายขึ้นเมื่อโหลดจากที่จัดเก็บแบบต่อเนื่อง) - สนับสนุนการทำซ้ำค่าที่เป็นไปได้ทั้งหมด

ข้อด้อย: - ไม่สนับสนุนคำเตือนการรวบรวมสำหรับการค้นหาที่ไม่ละเอียด (ทำให้การจับคู่รูปแบบเหมาะสมน้อยกว่า)

กรณีวัตถุ / ลักษณะที่ปิดผนึก

ข้อดี: - ด้วยการใช้คุณสมบัติที่ปิดสนิทเราสามารถสร้างอินสแตนซ์ของค่าล่วงหน้าบางค่าได้ขณะที่ค่าอื่นสามารถถูกฉีดได้ในเวลาการสร้าง - รองรับการจับคู่รูปแบบเต็มรูปแบบ

ข้อด้อย: - ตรวจสอบจากร้านค้าแบบถาวร - คุณมักจะต้องใช้การจับคู่รูปแบบที่นี่หรือกำหนดรายการของคุณเองที่เป็นไปได้ทั้งหมด 'ค่า enum'

สิ่งที่ทำให้ฉันเปลี่ยนความคิดเห็นในท้ายที่สุดคือสิ่งที่ตัวอย่างต่อไปนี้:

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โทรนั้นน่าเกลียด - ใช้การแจงนับแทนฉันสามารถเรียกเมธอด withName บนการแจงนับดังนี้:

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)
  }
}

ดังนั้นฉันคิดว่าการตั้งค่าของฉันไปข้างหน้าคือการใช้การแจงนับเมื่อค่ามีวัตถุประสงค์ที่จะเข้าถึงได้จากพื้นที่เก็บข้อมูลและกรณีวัตถุ / ลักษณะผนึก


ฉันสามารถดูว่ารูปแบบโค้ดที่สองเป็นที่ต้องการอย่างไร (กำจัดวิธีใช้ตัวช่วยสองวิธีจากรูปแบบโค้ดแรก) อย่างไรก็ตามฉันคิดหาวิธีที่คุณไม่ได้ถูกบังคับให้เลือกระหว่างสองรูปแบบนี้ ฉันครอบคลุมโดเมนทั้งหมดในคำตอบที่ฉันโพสต์ในกระทู้นี้: stackoverflow.com/a/25923651/501113
chaotic3quilibrium

2

ฉันชอบcase objects(มันเป็นเรื่องของการตั้งค่าส่วนตัว) เพื่อจัดการกับปัญหาที่เกิดขึ้นกับวิธีการนั้น (แยกวิเคราะห์สตริงและวนซ้ำตามองค์ประกอบทั้งหมด) ฉันได้เพิ่มบางบรรทัดที่ไม่สมบูรณ์แบบ แต่มีประสิทธิภาพ

ฉันกำลังวางรหัสที่นี่โดยคาดหวังว่ามันจะมีประโยชน์และคนอื่น ๆ ก็สามารถปรับปรุงได้

/**
 * 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 }
}

0

สำหรับผู้ที่ยังคงมองหาวิธีที่จะได้รับคำตอบของ GatesDa ให้ทำงาน : คุณสามารถอ้างอิงวัตถุเคสหลังจากที่ประกาศให้ยกตัวอย่าง:

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
}

0

ผมคิดว่าประโยชน์ที่ใหญ่ที่สุดของการมีcase classesมากกว่าenumerationsคือการที่คุณสามารถใช้รูปแบบคลาสประเภท aka เฉพาะกิจ polymorphysm ไม่จำเป็นต้องจับคู่ enums เช่น:

someEnum match {
  ENUMA => makeThis()
  ENUMB => makeThat()
}

แต่คุณจะมีสิ่งที่ชอบ:

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() = ...
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.