जावा के एनम के साथ एक सिंगलटन को लागू करने के डाउनसाइड क्या हैं?


14

परंपरागत रूप से, एक सिंगलटन आमतौर पर के रूप में लागू किया जाता है

public class Foo1
{
    private static final Foo1 INSTANCE = new Foo1();

    public static Foo1 getInstance(){ return INSTANCE; }

    private Foo1(){}

    public void doo(){ ... }
}

जावा के एनम के साथ, हम एक सिंगलटन को लागू कर सकते हैं

public enum Foo2
{
    INSTANCE;

    public void doo(){ ... }
}

दूसरा संस्करण जितना भयानक है, क्या इसमें कोई कमी है?

(मैंने इसे कुछ विचार दिए और मैं अपने स्वयं के प्रश्न का उत्तर दूंगा; उम्मीद है कि आपके पास बेहतर उत्तर होंगे)


16
नकारात्मक पक्ष यह है कि यह एक सिंगलटन है। पूरी तरह से ओवररेटेड ( खांसी ) "पैटर्न"
थॉमस ईडिंग

जवाबों:


32

एनम एकल के साथ कुछ समस्याएं:

एक कार्यान्वयन रणनीति के लिए प्रतिबद्ध

आमतौर पर, "सिंगलटन" एक कार्यान्वयन रणनीति को संदर्भित करता है, एपीआई विनिर्देश नहीं। Foo1.getInstance()सार्वजनिक रूप से यह घोषित करना बहुत दुर्लभ है कि यह हमेशा एक ही उदाहरण लौटाएगा। यदि आवश्यक हो, के कार्यान्वयन को Foo1.getInstance()विकसित कर सकते हैं, उदाहरण के लिए, प्रति धागा एक उदाहरण वापस करने के लिए।

साथ Foo2.INSTANCEहम सार्वजनिक रूप से घोषणा की कि इस उदाहरण है उदाहरण है, और वहाँ है कि बदलने के लिए कोई मौका नहीं है। एकल उदाहरण होने की कार्यान्वयन रणनीति उजागर और के लिए प्रतिबद्ध है।

यह समस्या अपंग नहीं है। उदाहरण के लिए, प्रभावी रूप से प्रति-थ्रेड उदाहरण के लिए, Foo2.INSTANCE.doo()थ्रेड स्थानीय सहायक ऑब्जेक्ट पर भरोसा कर सकते हैं ।

विस्तार Enum वर्ग

Foo2एक सुपर क्लास बढ़ाता है Enum<Foo2>। हम आमतौर पर सुपर क्लास से बचना चाहते हैं; विशेष रूप से इस मामले में, जिस सुपर क्लास को मजबूर किया गया है Foo2उसका कोई लेना-देना नहीं Foo2है। यह हमारे आवेदन के प्रकार पदानुक्रम के लिए एक प्रदूषण है। यदि हम वास्तव में एक सुपर क्लास चाहते हैं, तो आमतौर पर यह एक एप्लीकेशन क्लास है, लेकिन हम Foo2सुपर क्लास तय नहीं कर सकते ।

Foo2कुछ मजेदार इंस्टेंस विधियों को विरासत में मिला है name(), cardinal(), compareTo(Foo2), जो केवल Foo2उपयोगकर्ताओं के लिए भ्रामक हैं । यहां तक ​​कि अगर इस पद्धति के इंटरफ़ेस में वांछनीय है, Foo2तो इसकी अपनी name()विधि नहीं हो सकती Foo2

Foo2 कुछ मज़ेदार स्टैटिक विधियाँ भी शामिल हैं

    public static Foo2[] values() { ... }
    public static Foo2 valueOf(String name) { ... }
    public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

जो उपयोगकर्ताओं के लिए निरर्थक प्रतीत होता है। एक सिंगलटन में आमतौर पर पल्बिक स्टैटिक तरीके नहीं होने चाहिए (इसके अलावा अन्य getInstance())

serializability

यह बहुत ही सामान्य है कि सिंगलनेट्स स्टेटफुल हों। ये एकलव्य आमतौर पर क्रमिक नहीं होने चाहिए। मैं किसी भी यथार्थवादी उदाहरण के बारे में नहीं सोच सकता, जहां यह एक वीएम से दूसरे वीएम में एक स्टेटफुल सिंगलटन को ट्रांसपोर्ट करने के लिए समझ में आता है; एक सिंगलटन का अर्थ है "एक वीएम के भीतर अद्वितीय", न कि "ब्रह्मांड में अद्वितीय"।

यदि सीरियलाइज़ेशन वास्तव में एक स्टेटफुल सिंगलटन के लिए समझ में आता है, तो सिंगलटन को स्पष्ट रूप से और सटीक रूप से यह निर्दिष्ट करना चाहिए कि किसी अन्य वीएम में एक सिंगलटन को डिसेर्बलाइज करने का क्या मतलब है, जहां एक ही प्रकार का एक सिंगलटन पहले से मौजूद हो सकता है।

Foo2स्वचालित रूप से एक सरलीकृत सरलीकरण / deserialization रणनीति के लिए प्रतिबद्ध है। वह तो बस हादसा होने का इंतजार है। यदि हमारे पास डेटा का एक पेड़ है जो वैचारिक रूप Foo2से VM1 के एक राज्य चर को t1 पर संदर्भित करता है , क्रमबद्धता / विचलन के माध्यम से मान एक अलग मूल्य बन जाता है - Foo2t2 पर VM2 में एक ही चर का मान , बग का अन्वेषण करने के लिए एक कठिन बनाता है। यह बग अनजाने में Foo1चुपचाप नहीं होगा ।

कोडिंग के प्रतिबंध

ऐसी चीजें हैं जो सामान्य कक्षाओं में की जा सकती हैं, लेकिन enumकक्षाओं में निषिद्ध हैं। उदाहरण के लिए, कंस्ट्रक्टर में एक स्थिर क्षेत्र तक पहुंचना। प्रोग्रामर को अधिक सावधान रहना होगा क्योंकि वह एक विशेष वर्ग में काम कर रहा है।

निष्कर्ष

एनम पर पिग्गीबैक करके, हम कोड की 2 लाइनों को बचाते हैं; लेकिन कीमत बहुत अधिक है, हमें सभी बैगेज और एनम के प्रतिबंधों को ले जाना होगा, हम अनजाने में एनम के "सुविधाओं" को अनजाने में परिणाम देते हैं। एकमात्र कथित लाभ - स्वचालित क्रमबद्धता - नुकसान का कारण बनता है।


2
-1: सीरियलाइजेशन की आपकी चर्चा गलत है। तंत्र सरल नहीं है क्योंकि निरंकुशता के दौरान एनमों को नियमित उदाहरणों से बहुत अलग तरीके से व्यवहार किया जाता है। वर्णित समस्या उत्पन्न नहीं होती है क्योंकि वास्तविक deserialization तंत्र एक "राज्य चर" को संशोधित नहीं करता है।
स्कारफ्रिज

भ्रम की इस मिसाल को देखें: coderanch.com/t/498782/java/java/…
irreputable

2
दरअसल जुड़ी हुई चर्चा मेरी बात पर बल देती है। मुझे समझाएं कि जिस समस्या को आप मौजूद होने का दावा करते हैं, उसे मैंने क्या समझा। कुछ वस्तु A एक दूसरी वस्तु B का संदर्भ लेती है। सिंगलटन उदाहरण S का संदर्भ B है। अब हम एनुम-आधारित सिंगलटन के पहले के क्रमबद्ध उदाहरण का वर्णन करते हैं (जो कि क्रमांकन के समय, B '! = B) को संदर्भित करता है। वास्तव में क्या होता है, कि ए और एस संदर्भ बी, क्योंकि बी 'धारावाहिक नहीं होगा। मुझे लगा कि आप व्यक्त करना चाहते हैं कि ए और एस एक ही वस्तु का संदर्भ नहीं देते हैं।
स्कारफ्रिज

1
शायद हम वास्तव में उसी समस्या के बारे में बात नहीं कर रहे हैं?
स्कारफ्रिज

1
@ केविन क्रुमविदे: Constructor<?> c=EnumType.class.getDeclaredConstructors()[0]; c.setAccessible(true); EnumType f=(EnumType)MethodHandles.lookup().unreflectConstructor(c).invokeExact("BAR", 1);(उदाहरण के लिए, वास्तव में एक अच्छा उदाहरण होगा: Constructor<?> c=Thread.State.class.getDeclaredConstructors()[0]; c.setAccessible(true); Thread.State f=(Thread.State)MethodHandles.lookup().unreflectConstructor(c).invokeExact("RUNNING_BACKWARD", -1);^), जावा 7 और जावा 8 के तहत परीक्षण किया गया ...
होल्गर

6

एक गणना उदाहरण वर्ग लोडर पर निर्भर है। यानी यदि आपके पास एक द्वितीय श्रेणी लोडर है जिसमें माता-पिता के रूप में प्रथम श्रेणी लोडर नहीं है, तो समान एनम वर्ग लोड करने पर आपको स्मृति में कई उदाहरण मिल सकते हैं।


कोड नमूना

निम्नलिखित एनम बनाएँ, और इसकी .class फ़ाइल को एक जार में रखें। (बेशक जार में सही पैकेज / फ़ोल्डर संरचना होगी)

package mad;
public enum Side {
  RIGHT, LEFT;
}

अब इस परीक्षण को चलाएं, यह सुनिश्चित करते हुए कि कक्षा के मार्ग पर उपरोक्त एनम की कोई प्रतिलिपि नहीं है:

@Test
public void testEnums() throws Exception
{
    final ClassLoader root = MadTest.class.getClassLoader();

    final File jar = new File("path to jar"); // Edit path
    assertTrue(jar.exists());
    assertTrue(jar.isFile());

    final URL[] urls = new URL[] { jar.toURI().toURL() };
    final ClassLoader cl1 = new URLClassLoader(urls, root);
    final ClassLoader cl2 = new URLClassLoader(urls, root);

    final Class<?> sideClass1 = cl1.loadClass("mad.Side");
    final Class<?> sideClass2 = cl2.loadClass("mad.Side");

    assertNotSame(sideClass1, sideClass2);

    assertTrue(sideClass1.isEnum());
    assertTrue(sideClass2.isEnum());
    final Field f1 = sideClass1.getField("RIGHT");
    final Field f2 = sideClass2.getField("RIGHT");
    assertTrue(f1.isEnumConstant());
    assertTrue(f2.isEnumConstant());

    final Object right1 = f1.get(null);
    final Object right2 = f2.get(null);
    assertNotSame(right1, right2);
}

और अब हमारे पास "समान" एनम मान का प्रतिनिधित्व करने वाले दो ऑब्जेक्ट हैं।

मैं मानता हूं कि यह एक दुर्लभ और वंचित कोने का मामला है, और लगभग हमेशा एक एनुम का उपयोग एक जावा सिंगलटन के लिए किया जा सकता है। मै स्वयं कर लूंगा। लेकिन संभावित डाउनसाइड के बारे में पूछे गए प्रश्न और सावधानी के इस नोट के बारे में जानने लायक है।


क्या आप उस चिंता का कोई संदर्भ पा सकते हैं?

मैंने अब अपने प्रारंभिक उत्तर को उदाहरण कोड के साथ संपादित और बढ़ा दिया है। उम्मीद है कि वे बिंदु को स्पष्ट करने में मदद करेंगे और साथ ही माइकलटी की क्वेरी का जवाब देंगे।
मैड जी

@ मिचेल्ट: मुझे उम्मीद है कि आपके सवाल का जवाब :-)
मैड जी

तो, अगर सिंगलटन के लिए एनम (वर्ग के बजाय) के उपयोग का कारण केवल इसकी सुरक्षा थी, तो अब इसके लिए कोई कारण नहीं है ... उत्कृष्ट, +1
गंगानुस

1
क्या "पारंपरिक" सिंगलटन कार्यान्वयन दो अलग-अलग वर्ग लोडर के साथ भी अपेक्षित व्यवहार करेगा?
रॉन क्लेन

3

Enum पैटर्न का उपयोग किसी भी वर्ग के लिए नहीं किया जा सकता है जो कि कंस्ट्रक्टर में एक अपवाद को फेंक देगा। यदि यह आवश्यक है, तो एक कारखाने का उपयोग करें:

class Elvis {
    private static Elvis self = null;
    private int age;

    private Elvis() throws Exception {
        ...
    }

    public synchronized Elvis getInstance() throws Exception {
        return self != null ? self : (self = new Elvis());
    }

    public int getAge() {
        return age;
    }        
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.