केस क्लास के साथी में ओवरराइड कैसे करें


84

तो यहाँ की स्थिति है। मैं एक केस क्लास को परिभाषित करना चाहता हूं:

case class A(val s: String)

और मैं यह सुनिश्चित करने के लिए एक वस्तु को परिभाषित करना चाहता हूं कि जब मैं कक्षा के उदाहरण बनाता हूं, तो 's' का मान हमेशा बड़ा होता है, जैसे:

object A {
  def apply(s: String) = new A(s.toUpperCase)
}

हालाँकि, यह काम नहीं करता है क्योंकि स्काला शिकायत कर रही है कि लागू (एस: स्ट्रिंग) विधि दो बार परिभाषित की गई है। मैं समझता हूं कि केस क्लास सिंटैक्स स्वचालित रूप से मेरे लिए इसे परिभाषित करेगा, लेकिन क्या कोई दूसरा तरीका नहीं है जिससे मैं इसे हासिल कर सकूं? मैं केस क्लास के साथ रहना चाहता हूँ क्योंकि मैं इसे पैटर्न मिलान के लिए उपयोग करना चाहता हूँ।


3
शायद शीर्षक बदलने के लिए "कैसे एक मामले वर्ग साथी में लागू करने के लिए ओवरराइड"
ziggystar

1
चीनी का उपयोग न करें यदि वह ऐसा नहीं करता है जो आप चाहते हैं ...
राफेल

7
@ राफेल यदि आप ब्राउन शुगर चाहते हैं, तो हम कुछ विशेष गुणों के साथ चीनी चाहते हैं .. मेरे पास ओपी के रूप में सटीक जांच है: केस क्लासेस उपयोगी हैं, लेकिन साथी वस्तु को सजाने के लिए यह एक सामान्य उपयोग मामला है। एक अतिरिक्त आवेदन।
StephenBoesch

FYI करें यह 2.12+ स्केला में नियत है। साथी में एक अन्यथा कंफ्लिक्टिंग एप्‍लीकेशन को परिभाषित करने से डिफॉल्ट एप्‍लीकेशन जेनरेट करने से रोकता है।
stewSquared

जवाबों:


90

संघर्ष का कारण यह है कि केस क्लास सटीक समान लागू () विधि (समान हस्ताक्षर) प्रदान करता है।

सबसे पहले मैं आपको सुझाव देना चाहूंगा कि आप का उपयोग करें:

case class A(s: String) {
  require(! s.toCharArray.exists( _.isLower ), "Bad string: "+ s)
}

यदि उपयोगकर्ता किसी ऐसे उदाहरण को बनाने का प्रयास करता है जिसमें निम्न केस वर्ण शामिल हैं, तो यह अपवाद को फेंक देगा। यह केस क्लासेस का एक अच्छा उपयोग है, क्योंकि आप कंस्ट्रक्टर में जो डालते हैं, वह वही होता है जब आप पैटर्न मिलान ( match) का उपयोग करते हैं ।

यदि यह वह नहीं है जो आप चाहते हैं, तो मैं कंस्ट्रक्टर privateबनाऊंगा और उपयोगकर्ताओं को केवल लागू पद्धति का उपयोग करने के लिए मजबूर करूंगा :

class A private (val s: String) {
}

object A {
  def apply(s: String): A = new A(s.toUpperCase)
}

जैसा कि आप देख रहे हैं, A अब नहीं है case class। मुझे यकीन नहीं है कि अगर अपरिवर्तनीय क्षेत्रों के साथ मामला कक्षाएं आने वाले मूल्यों के संशोधन के लिए हैं, तो नाम "केस क्लास" का अर्थ है कि (अनमॉडिफाइड) कंस्ट्रक्टर तर्कों का उपयोग करना संभव हो सकता है match


5
toCharArrayकॉल आवश्यक नहीं है, आप भी लिख सकता है s.exists(_.isLower)
फ्रैंक एस। थॉमस

4
BTW मुझे लगता s.forall(_.isUpper)है कि समझने में आसान है !s.exists(_.isLower)
फ्रैंक एस। थॉमस

धन्यवाद! यह निश्चित रूप से मेरी जरूरतों के लिए काम करता है। @ फ्रेंक, मैं सहमत हूं कि s.forall(_isupper)पढ़ना आसान है। मैं @ ओल के सुझाव के साथ संयोजन के रूप में उपयोग करूँगा।
जॉन एस

4
"नाम" केस क्लास के लिए +1 का तात्पर्य यह है कि (अनमॉडिफाइड) कंस्ट्रक्टर के तर्कों का उपयोग करना संभव हो match
यूजेन लबुन

2
@ollekullberg आपको ओपी के वांछित प्रभाव को प्राप्त करने के लिए एक केस क्लास (और एक केस केस क्लास डिफ़ॉल्ट रूप से प्रदान करता है सभी अतिरिक्त उपहार खोना) से दूर जाने की ज़रूरत नहीं है। यदि आप दो संशोधन करते हैं, तो आप अपना केस क्लास कर सकते हैं और इसे खा भी सकते हैं! ए) केस क्लास को सार के रूप में चिह्नित करें और बी) केस क्लास कंस्ट्रक्टर को निजी [ए] के रूप में चिह्नित करें (केवल निजी के विपरीत)। इस तकनीक का उपयोग करते हुए मामले की कक्षाओं के आसपास कुछ और अधिक सूक्ष्म मुद्दे हैं। कृपया देखें कि मैंने और अधिक विस्तृत विवरणों के लिए पोस्ट किया है: stackoverflow.com/a/25538287/501113
chaotic3quilibrium

28

अद्यतन २०१६/०२/२५:
जबकि मैंने जो उत्तर नीचे लिखा था, वह पर्याप्त है, यह मामला वर्ग के साथी वस्तु के संबंध में एक और संबंधित उत्तर का उल्लेख करने योग्य है। अर्थात्, कंपाइलर उत्पन्न निहित साथी वस्तु को वास्तव में कैसे पुन: उत्पन्न करता है जो तब होता है जब कोई केवल केस क्लास को ही परिभाषित करता है। मेरे लिए, यह जवाबी सहजता से निकला।


सारांश:
आप केस क्लास के पैरामीटर के मान को केस क्लास में संग्रहीत करने से पहले बहुत आसानी से बदल सकते हैं, जबकि यह अभी भी एक मान्य (एटेड) एडीटी (सार डेटा प्रकार) शेष है। जबकि समाधान अपेक्षाकृत सरल था, विवरणों की खोज करना थोड़ा अधिक चुनौतीपूर्ण था।

विवरण:
यदि आप यह सुनिश्चित करना चाहते हैं कि आपके केस क्लास के केवल मान्य इंस्टेंट को कभी भी तत्काल किया जा सकता है जो कि ADT (अमूर्त डेटा टाइप) के पीछे एक आवश्यक धारणा है, तो आपको बहुत सी चीजें करनी चाहिए।

उदाहरण के लिए, कंपाइलर जेनरेट की गई copyविधि किसी केस क्लास पर डिफ़ॉल्ट रूप से प्रदान की जाती है। इसलिए, भले ही आप यह सुनिश्चित करने के लिए बहुत सतर्क थे कि केवल स्पष्ट साथी ऑब्जेक्ट applyविधि के माध्यम से बनाया गया था, जो गारंटी देता था कि वे केवल ऊपरी मामले मानों को शामिल कर सकते हैं, निम्न कोड निम्न केस वैल्यू के साथ केस क्लास इंस्टेंस उत्पन्न करेगा:

val a1 = A("Hi There") //contains "HI THERE"
val a2 = a1.copy(s = "gotcha") //contains "gotcha"

इसके अतिरिक्त, केस कक्षाएं लागू होती हैं java.io.Serializable। इसका मतलब यह है कि केवल ऊपरी मामले में आपके सावधान रणनीति को सरल पाठ संपादक और डीरियलाइज़ेशन के साथ बदल दिया जा सकता है।

इसलिए, सभी विभिन्न तरीकों के लिए आपके केस क्लास का उपयोग किया जा सकता है (परोपकारी और / या पुरुष रूप से), यहां वे क्रियाएं हैं जिन्हें आपको लेना चाहिए:

  1. अपने स्पष्ट साथी वस्तु के लिए:
    1. अपने केस क्लास के समान नाम का उपयोग करके इसे बनाएं
      • इससे केस क्लास के प्राइवेट पार्ट्स तक पहुंच है
    2. applyअपने केस क्लास के लिए प्राथमिक कंस्ट्रक्टर के समान हस्ताक्षर के साथ एक विधि बनाएं
      • एक बार 2.1 चरण पूरा होने पर यह सफलतापूर्वक संकलन करेगा
    3. newऑपरेटर का उपयोग करके केस क्लास का एक उदाहरण प्राप्त करने वाला एक कार्यान्वयन प्रदान करें और एक खाली कार्यान्वयन प्रदान करें{}
      • यह अब आपकी शर्तों पर केस क्लास को सख्ती से लागू करेगा
      • खाली कार्यान्वयन {}प्रदान किया जाना चाहिए क्योंकि मामला वर्ग घोषित किया गया है abstract(चरण 2.1 देखें)
  2. आपके केस क्लास के लिए:
    1. इसकी घोषणा करें abstract
      • स्केल कंपाइलर applyको साथी ऑब्जेक्ट में एक विधि उत्पन्न करने से रोकता है जो कि "विधि को दो बार परिभाषित किया गया है ..." संकलन त्रुटि (चरण 1.2 ऊपर) है
    2. प्राथमिक निर्माता के रूप में चिह्नित करें private[A]
      • प्राथमिक कंस्ट्रक्टर अब केवल केस क्लास और उसके साथी ऑब्जेक्ट के लिए उपलब्ध है (जिसे हमने चरण 1.1 में ऊपर परिभाषित किया है)
    3. एक readResolveविधि बनाएँ
      1. लागू विधि का उपयोग करके एक कार्यान्वयन प्रदान करें (चरण 1.2 ऊपर)
    4. एक copyविधि बनाएँ
      1. इसे परिभाषित करें कि केस क्लास के प्राथमिक कंस्ट्रक्टर के समान ही हस्ताक्षर हैं
      2. प्रत्येक पैरामीटर के लिए, एक ही पैरामीटर नाम का उपयोग करके डिफ़ॉल्ट मूल्य जोड़ने (पूर्व: s: String = s)
      3. लागू पद्धति का उपयोग करके एक कार्यान्वयन प्रदान करें (चरण 1.2 नीचे)

यहां उपरोक्त क्रियाओं के साथ आपका कोड संशोधित किया गया है:

object A {
  def apply(s: String, i: Int): A =
    new A(s.toUpperCase, i) {} //abstract class implementation intentionally empty
}
abstract case class A private[A] (s: String, i: Int) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    A.apply(s, i)
  def copy(s: String = s, i: Int = i): A =
    A.apply(s, i)
}

और आवश्यकता को लागू करने के बाद यहां आपका कोड है (@ollekullberg उत्तर में सुझाव दिया गया है) और किसी भी प्रकार की कैशिंग डालने के लिए आदर्श स्थान की पहचान करना:

object A {
  def apply(s: String, i: Int): A = {
    require(s.forall(_.isUpper), s"Bad String: $s")
    //TODO: Insert normal instance caching mechanism here
    new A(s, i) {} //abstract class implementation intentionally empty
  }
}
abstract case class A private[A] (s: String, i: Int) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    A.apply(s, i)
  def copy(s: String = s, i: Int = i): A =
    A.apply(s, i)
}

और यह संस्करण अधिक सुरक्षित / मजबूत है यदि इस कोड का उपयोग जावा इंटरॉप के माध्यम से किया जाएगा (केस क्लास को कार्यान्वयन के रूप में छिपाता है और एक अंतिम वर्ग बनाता है जो व्युत्पत्तियों को रोकता है):

object A {
  private[A] abstract case class AImpl private[A] (s: String, i: Int)
  def apply(s: String, i: Int): A = {
    require(s.forall(_.isUpper), s"Bad String: $s")
    //TODO: Insert normal instance caching mechanism here
    new A(s, i)
  }
}
final class A private[A] (s: String, i: Int) extends A.AImpl(s, i) {
  private def readResolve(): Object = //to ensure validation and possible singleton-ness, must override readResolve to use explicit companion object apply method
    A.apply(s, i)
  def copy(s: String = s, i: Int = i): A =
    A.apply(s, i)
}

हालांकि यह सीधे आपके प्रश्न का उत्तर देता है, उदाहरण के कैशिंग से परे केस क्लासेस के आसपास इस मार्ग का विस्तार करने के और भी तरीके हैं। अपनी स्वयं की परियोजना आवश्यकताओं के लिए, मैंने एक और भी अधिक विस्तृत समाधान तैयार किया है, जिसे मैंने CodeReview (एक StackOverflow बहन साइट) पर प्रलेखित किया है । यदि आप मेरे समाधान का उपयोग करते हुए या इसका लाभ उठाते हुए इसे समाप्त करते हैं, तो कृपया मुझे प्रतिक्रिया, सुझाव या प्रश्न छोड़ने का विचार करें और इस कारण से, मैं एक दिन में जवाब देने की पूरी कोशिश करूंगा।


मैंने अभी हाल ही में स्कैला मुहावरेदार होने के लिए और आसानी से कैशिंग केस इंस्टेंसेस के लिए स्केलाचे का उपयोग करने के लिए एक नया विस्तारक समाधान पोस्ट किया था (मेटा नियमों के अनुसार मौजूदा उत्तर को संपादित करने की अनुमति नहीं दी गई): codereview.stackexexhange.com/a/98367/4758
चेट्टीक्विलीब्रियम

इस विस्तृत विवरण के लिए धन्यवाद। लेकिन, मैं यह समझने के लिए संघर्ष कर रहा हूं, क्यों readResolve कार्यान्वयन की आवश्यकता है। क्योंकि संकलन भी readResolve कार्यान्वयन के बिना काम करता है।
मोगली

एक अलग सवाल पोस्ट किया: stackoverflow.com/questions/32236594/…
मोगली

12

मुझे नहीं पता कि applyसाथी ऑब्जेक्ट में विधि को कैसे ओवरराइड किया जा सकता है (यदि यह भी संभव है), लेकिन आप ऊपरी केस स्ट्रिंग्स के लिए एक विशेष प्रकार का भी उपयोग कर सकते हैं:

class UpperCaseString(s: String) extends Proxy {
  val self: String = s.toUpperCase
}

implicit def stringToUpperCaseString(s: String) = new UpperCaseString(s)
implicit def upperCaseStringToString(s: UpperCaseString) = s.self

case class A(val s: UpperCaseString)

println(A("hello"))

उपरोक्त कोड आउटपुट:

A(HELLO)

इस सवाल पर भी आपकी नज़र होनी चाहिए और इसका जवाब है: स्काला: क्या डिफॉल्ट केस क्लास कंस्ट्रक्टर को ओवरराइड करना संभव है?


इसके लिए धन्यवाद - मैं उसी तर्ज पर सोच रहा था, लेकिन इसके बारे में नहीं जानता था Proxy! हालांकि s.toUpperCase एक बार बेहतर हो सकता है ।
बेन जैक्सन

@ मैं नहीं देखता कि toUpperCaseएक बार से अधिक कहाँ बुलाया जाता है।
फ्रैंक एस। थॉमस

तुम काफी सही हो val self, नहीं def self। मैं अभी दिमाग पर C ++ मिला है।
बेन जैक्सन

6

अप्रैल 2017 के बाद इसे पढ़ने वाले लोगों के लिए: स्कला 2.12.2+ के रूप में, स्केला ओवरराइडिंग को लागू करने और डिफ़ॉल्ट रूप से अनपेक्षित रूप से अनुमति देता है । आप इस व्यवहार -Xsource:2.12को Scala 2.11.11+ पर कंपाइलर को विकल्प देकर प्राप्त कर सकते हैं ।


1
इसका क्या मतलब है? मैं इस ज्ञान को एक समाधान पर कैसे लागू कर सकता हूं? क्या आप एक उदाहरण प्रदान कर सकते हैं?
k0pernikus

ध्यान दें कि पैटर्न मिलान केस कक्षाओं के लिए अनपेक्षित रूप से उपयोग नहीं किया जाता है, जो इसे काफी हद तक बेकार कर देता है (यदि आप -Xprintएक matchबयान देखेंगे कि आप देखेंगे कि इसका उपयोग नहीं किया गया है)।
जे क्रैकनेल

5

यह चर के साथ काम करता है:

case class A(var s: String) {
   // Conversion
   s = s.toUpperCase
}

यह अभ्यास जाहिरा तौर पर किसी अन्य निर्माता को परिभाषित करने के बजाय मामले की कक्षाओं में प्रोत्साहित किया जाता है। यहाँ देखें। । किसी ऑब्जेक्ट की प्रतिलिपि बनाते समय, आप उसी संशोधनों को भी रखते हैं।


4

केस क्लास को रखने और कोई निहित दोष या किसी अन्य कंस्ट्रक्टर को रखने के दौरान एक अन्य विचार यह है कि applyउपयोगकर्ता के दृष्टिकोण से थोड़ा अलग, लेकिन एक ही के हस्ताक्षर बनाने के लिए । कहीं न कहीं मैंने निहित चाल देखी है, लेकिन याद नहीं किया जा सकता है कि कौन सा अंतर्निहित तर्क था, इसलिए मैंने Booleanयहां चुना । अगर कोई मेरी मदद कर सकता है और चाल खत्म कर सकता है ...

object A {
  def apply(s: String)(implicit ev: Boolean) = new A(s.toLowerCase)
}
case class A(s: String)

कॉल साइटों पर यह आपको एक संकलित त्रुटि (अतिभारित परिभाषा के अस्पष्ट संदर्भ) देगा। यह केवल तभी काम करता है जब स्केला के प्रकार अलग होते हैं, लेकिन एक ही समय में मिटाए जाते हैं, उदाहरण के लिए एक सूची [इंट] और एक सूची [स्ट्रिंग] के लिए दो अलग-अलग कार्य करने के लिए।
मिकाइल मेयर

मैं काम करने के लिए इस समाधान मार्ग को प्राप्त नहीं कर सका (2.11 के साथ)। मैंने आखिरकार इस बात पर काम किया कि वह स्पष्ट साथी वस्तु पर अपनी स्वयं की लागू विधि क्यों प्रदान नहीं कर सकता मैंने इसे केवल मेरे द्वारा
chaotic3quilibrium

3

मैंने उसी समस्या का सामना किया और यह समाधान मेरे लिए ठीक है:

sealed trait A {
  def s:String
}

object A {
  private case class AImpl(s:String)
  def apply(s:String):A = AImpl(s.toUpperCase)
}

और, यदि किसी भी विधि की आवश्यकता है, तो इसे केवल विशेषता में परिभाषित करें और इसे केस क्लास में ओवरराइड करें।


0

यदि आप पुराने स्लैला के साथ फंस गए हैं जहाँ आप डिफ़ॉल्ट रूप से ओवरराइड नहीं कर सकते हैं या आप कंपाइलर ध्वज को @ mehmet-emre के रूप में नहीं जोड़ना चाहते हैं, और आपको केस क्लास की आवश्यकता है, तो आप निम्न कार्य कर सकते हैं:

case class A(private val _s: String) {
  val s = _s.toUpperCase
}

0

स्कैला 2.13 पर 2020 तक, एक ही हस्ताक्षर के काम के साथ केस क्लास लागू करने की विधि के ओवरराइड करने का उपरोक्त परिदृश्य पूरी तरह से ठीक है।

case class A(val s: String)

object A {
  def apply(s: String) = new A(s.toUpperCase)
}

उपरोक्त स्निपेट संकलन और स्केल में ठीक चलता है। 2.13 दोनों आरईपीएल और गैर-आरईपीएल मोड में।


-2

मुझे लगता है कि यह ठीक उसी तरह काम करता है जैसे आप इसे पहले से ही चाहते हैं। यहाँ मेरा REPL सत्र है:

scala> case class A(val s: String)
defined class A

scala> object A {
     | def apply(s: String) = new A(s.toUpperCase)
     | }
defined module A

scala> A("hello")
res0: A = A(HELLO)

यह Scala 2.8.1.final का उपयोग कर रहा है


3
अगर मैं कोड को फाइल में रखता हूं और उसे संकलित करने की कोशिश करता हूं तो यह यहां काम नहीं करता है।
फ्रैंक एस। थॉमस

मेरा मानना ​​है कि मैंने पहले के उत्तर में भी ऐसा ही कुछ सुझाया था और किसी ने कहा था कि यह उत्तर में काम करता है जिस तरह से काम करता है।
बेन जैक्सन

5
आरईपीएल अनिवार्य रूप से प्रत्येक पंक्ति के साथ पिछले एक के अंदर एक नया दायरा बनाता है। इसीलिए जब REPL से आपके कोड में पेस्ट किया जाता है तो कुछ चीजें अपेक्षा के अनुरूप काम नहीं करती हैं। इसलिए, हमेशा दोनों की जांच करें।
20

1
उपरोक्त कोड (जो काम नहीं करता है) का परीक्षण करने का उचित तरीका है: केस और ऑब्जेक्ट दोनों को एक साथ परिभाषित करने के लिए REPL में पेस्ट करें।
StephenBoesch
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.