क्या उन वस्तुओं का होना ठीक है जो खुद को डाली हैं, भले ही यह उनके उपवर्गों के एपीआई को प्रदूषित करती हो?


33

मैं एक आधार वर्ग है Base। इसके दो उपवर्ग हैं, Sub1और Sub2। प्रत्येक उपवर्ग में कुछ अतिरिक्त विधियाँ हैं। उदाहरण के लिए, Sub1है Sandwich makeASandwich(Ingredients... ingredients), और Sub2है boolean contactAliens(Frequency onFrequency)

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

Baseअधिकांश कार्यक्षमता प्रदान करता है, और मेरे पास Baseवस्तुओं का एक बड़ा संग्रह है । हालांकि, सभी Baseवस्तुओं या तो एक कर रहे हैं Sub1या एक Sub2, और कभी कभी मुझे पता है कि जो वे कर रहे हैं की जरूरत है।

यह निम्नलिखित करने के लिए एक बुरे विचार की तरह लगता है:

for (Base base : bases) {
    if (base instanceof Sub1) {
        ((Sub1) base).makeASandwich(getRandomIngredients());
        // ... etc.
    } else { // must be Sub2
        ((Sub2) base).contactAliens(getFrequency());
        // ... etc.
    }
}

इसलिए मैं बिना कास्टिंग के इससे बचने की रणनीति के साथ आया। Baseअब ये तरीके हैं:

boolean isSub1();
Sub1 asSub1();
Sub2 asSub2();

और हां, Sub1इन तरीकों को लागू करता है

boolean isSub1() { return true; }
Sub1 asSub1();   { return this; }
Sub2 asSub2();   { throw new IllegalStateException(); }

और Sub2उन्हें विपरीत तरीके से लागू करता है।

दुर्भाग्य से, अब Sub1और Sub2इन विधियों के अपने एपीआई में हैं। इसलिए मैं यह कर सकता हूं, उदाहरण के लिए, पर Sub1

/** no need to use this if object is known to be Sub1 */
@Deprecated
boolean isSub1() { return true; }

/** no need to use this if object is known to be Sub1 */
@Deprecated
Sub1 asSub1();   { return this; }

/** no need to use this if object is known to be Sub1 */
@Deprecated
Sub2 asSub2();   { throw new IllegalStateException(); }

इस प्रकार, यदि वस्तु को केवल a के रूप में जाना जाता है Base, तो ये विधियाँ बिना किसी को बताए की जाती हैं, और इसका उपयोग खुद को "अलग" करने के लिए किया जा सकता है, ताकि मैं इस पर उपवर्ग के तरीकों को लागू कर सकूं। यह मुझे एक तरह से सुरुचिपूर्ण लगता है, लेकिन दूसरी ओर, मैं एक वर्ग से "हटाने" के तरीके के रूप में डिप्रेस्ड एनोटेशन का दुरुपयोग कर रहा हूं।

चूंकि एक Sub1उदाहरण वास्तव में एक आधार है, इसलिए यह इनकैप्सुलेशन के बजाय विरासत का उपयोग करने के लिए समझ में आता है। क्या मैं अच्छा कर रहा हूँ? क्या इस समस्या को हल करने का एक बेहतर तरीका है?


12
सभी 3 वर्गों को अब एक दूसरे के बारे में जानना है। सब 3 को जोड़ने पर बहुत सारे कोड परिवर्तन होंगे, और सब 10 को जोड़ने पर यह काफी दर्दनाक होगा
Dan Pichelman

15
यदि आपने हमें वास्तविक कोड दिया तो यह बहुत मदद करेगा। ऐसी परिस्थितियाँ हैं जहाँ किसी चीज़ के विशेष वर्ग के आधार पर निर्णय लेना उचित है, लेकिन यह बताना असंभव है कि क्या आप इस तरह के आकस्मिक उदाहरणों के साथ क्या कर रहे हैं। जो कुछ भी इसके लायक है, आप जो चाहते हैं वह आगंतुक या टैग की गई यूनियन है
डोभाल

1
क्षमा करें, मुझे लगा कि इससे सरल उदाहरण देने में आसानी होगी। हो सकता है कि मैं जो करना चाहता हूं उसके व्यापक दायरे के साथ नया प्रश्न पोस्ट करूं।
कोडब्रेकर

12
आप केवल कास्टिंग को फिर से लागू कर रहे हैं और instanceofएक तरह से जिसमें बहुत सारे टाइपिंग की आवश्यकता होती है, त्रुटि-प्रवण है, और अधिक उपवर्गों को जोड़ना कठिन बनाता है।
user253751

5
यदि Sub1और Sub2कहीं भी इस्तेमाल नहीं किया जा सकता है, तो आप उन्हें इस तरह क्यों मानते हैं? अपने 'सैंडविच बनाने वालों' और 'एलियन-कॉन्टैक्टर्स' पर अलग से नज़र क्यों नहीं रखते?
::

जवाबों:


27

यह हमेशा आधार वर्ग के कार्यों को जोड़ने के लिए समझ में नहीं आता है, जैसा कि कुछ अन्य उत्तरों में सुझाया गया है। बहुत से विशेष केस फ़ंक्शंस जोड़ने से परिणामस्वरूप असंबद्ध घटकों को एक साथ जोड़ा जा सकता है।

उदाहरण के लिए मेरे पास एक Animalवर्ग हो सकता है , Catऔर Dogघटकों के साथ । अगर मैं उन्हें प्रिंट करने में सक्षम होना चाहता हूं, या उन्हें जीयूआई में दिखाना चाहता हूं तो मुझे जोड़ने renderToGUI(...)और sendToPrinter(...)आधार वर्ग के लिए ओवरकिल हो सकता है ।

टाइप-चेक और कास्ट का उपयोग करते हुए आप जिस दृष्टिकोण का उपयोग कर रहे हैं, वह नाजुक है - लेकिन कम से कम चिंताओं को अलग रखता है।

हालाँकि यदि आप स्वयं को अक्सर इस प्रकार के चेक / कास्ट करते हुए पाते हैं तो एक विकल्प यह है कि इसके लिए विज़िटर / डबल-डिस्पैच पैटर्न को लागू किया जाए। यह इस तरह दिखता है:

public abstract class Base {
  ...
  abstract void visit( BaseVisitor visitor );
}

public class Sub1 extends Base {
  ...
  void visit(BaseVisitor visitor) { visitor.onSub1(this); }
}

public class Sub2 extends Base {
  ...
  void visit(BaseVisitor visitor) { visitor.onSub2(this); }
}

public interface BaseVisitor {
   void onSub1(Sub1 that);
   void onSub2(Sub2 that);
}

अब आपका कोड बन गया

public class ActOnBase implements BaseVisitor {
    void onSub1(Sub1 that) {
       that.makeASandwich(getRandomIngredients())
    }

    void onSub2(Sub2 that) {
       that.contactAliens(getFrequency());
    }
}

BaseVisitor visitor = new ActOnBase();
for (Base base : bases) {
    base.visit(visitor);
}

मुख्य लाभ यह है कि यदि आप एक उपवर्ग जोड़ते हैं, तो आप चुपचाप गायब मामलों के बजाय संकलन त्रुटियों को प्राप्त करेंगे। नए विज़िटर वर्ग भी कार्यों को खींचने के लिए एक अच्छा लक्ष्य बन जाता है। उदाहरण के लिए यह समझ getRandomIngredients()में आ सकता है ActOnBase

आप लूपिंग तर्क भी निकाल सकते हैं: उदाहरण के लिए उपरोक्त टुकड़ा बन सकता है

BaseVisitor.applyToArray(bases, new ActOnBase() );

थोड़ा और मालिश और जावा 8 के लैम्ब्डा और स्ट्रीमिंग का उपयोग करने से आपको मिलेगा

bases.stream()
     .forEach( BaseVisitor.forEach(
       Sub1 that -> that.makeASandwich(getRandomIngredients()),
       Sub2 that -> that.contactAliens(getFrequency())
     ));

कौन सा IMO दिखने में काफी सुंदर और रसीला है जितना आप पा सकते हैं।

यहाँ एक अधिक पूर्ण जावा 8 उदाहरण है:

public static abstract class Base {
    abstract void visit( BaseVisitor visitor );
}

public static class Sub1 extends Base {
    void visit(BaseVisitor visitor) { visitor.onSub1(this); }

    void makeASandwich() {
        System.out.println("making a sandwich");
    }
}

public static class Sub2 extends Base {
    void visit(BaseVisitor visitor) { visitor.onSub2(this); }

    void contactAliens() {
        System.out.println("contacting aliens");
    }
}

public interface BaseVisitor {
    void onSub1(Sub1 that);
    void onSub2(Sub2 that);

    static Consumer<Base> forEach(Consumer<Sub1> sub1, Consumer<Sub2> sub2) {

        return base -> {
            BaseVisitor baseVisitor = new BaseVisitor() {

                @Override
                public void onSub1(Sub1 that) {
                    sub1.accept(that);
                }

                @Override
                public void onSub2(Sub2 that) {
                    sub2.accept(that);
                }
            };
            base.visit(baseVisitor);
        };
    }
}

Collection<Base> bases = Arrays.asList(new Sub1(), new Sub2());

bases.stream()
     .forEach(BaseVisitor.forEach(
             Sub1::makeASandwich,
             Sub2::contactAliens));

+1: इस तरह की चीज़ों का इस्तेमाल आमतौर पर उन भाषाओं में किया जाता है, जिनमें योग-प्रकार और पैटर्न मिलान के लिए प्रथम श्रेणी का समर्थन होता है, और यह जावा में एक मान्य डिज़ाइन है, भले ही यह वाक्यगत रूप से बहुत बदसूरत हो।
मकारसे

@ मेनकार थोड़ी सी सिन्क्रेटिक शक्कर इसे बदसूरत से बहुत दूर कर सकती है - मैंने इसे एक उदाहरण के साथ अपडेट किया है।
माइकल एंडरसन

मान लीजिए कि काल्पनिक रूप से ओपी अपना दिमाग बदलता है और ए जोड़ने का फैसला करता है Sub3। क्या विज़िटर के पास ठीक वैसी ही समस्या नहीं है instanceof? अब आपको onSub3आगंतुक को जोड़ने की आवश्यकता है और यदि आप भूल जाते हैं तो यह टूट जाता है।
रेडियोएडफ़

1
@Radiodef सीधे टाइप पर स्विच करने पर विज़िटर पैटर्न का उपयोग करने का एक फायदा यह है कि एक बार जब आप onSub3विज़िटर इंटरफ़ेस में जुड़ जाते हैं, तो आपको हर उस स्थान पर एक संकलन त्रुटि मिलती है जिसे आप एक नया विज़िटर बनाते हैं जो अभी तक अपडेट नहीं किया गया है। इसके विपरीत प्रकार पर स्विच करने से रन-टाइम त्रुटि उत्पन्न होगी - जो ट्रिगर करने के लिए मुश्किल हो सकती है।
माइकल एंडरसन

1
@FiveNine, यदि आप उत्तर के अंत में उस पूर्ण उदाहरण को जोड़ने के लिए खुश हैं जो दूसरों की मदद कर सकता है।
माइकल एंडरसन

83

मेरे दृष्टिकोण से: आपका डिज़ाइन गलत है

प्राकृतिक भाषा में अनुवादित, आप निम्नलिखित कह रहे हैं:

यह देखते हुए कि हमारे पास animalsहैं catsऔर हैं fishanimalsगुण हैं, जो आम हैं catsऔर fish। लेकिन यह पर्याप्त नहीं है: कुछ गुण हैं, जो इससे अलग catहैं fish, इसलिए आपको उपवर्ग की आवश्यकता है।

अब आपको समस्या है, कि आप आंदोलन करना भूल गए । ठीक है। यह अपेक्षाकृत आसान है:

for(Animal a : animals){
   if (a instanceof Fish) swim();
   if (a instanceof Cat) walk();
}

लेकिन यह एक गलत डिजाइन है। सही तरीका होगा:

for(Animal a : animals){
    animal.move()
}

moveप्रत्येक पशु द्वारा अलग-अलग तरीके से लागू किया गया साझा व्यवहार कहां होगा।

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

इसका मतलब है: आपका डिज़ाइन टूट गया है।

मेरी सिफारिश: रिफ्लेक्टर Base, Sub1और Sub2


8
यदि आप वास्तविक कोड प्रस्तुत करते हैं, तो मैं सिफारिशें कर सकता हूं।
थॉमस जंक

9
@codebreaker यदि आप सहमत हैं कि आपका उदाहरण अच्छा नहीं था, तो मैं इस उत्तर को स्वीकार करने और फिर एक नया प्रश्न लिखने की सलाह देता हूं। एक अलग उदाहरण के लिए प्रश्न को संशोधित न करें या मौजूदा उत्तरों से कोई मतलब नहीं होगा।
मोबी डिस्क

16
@codebreaker मुझे लगता है कि यह असली सवाल का जवाब देकर इस समस्या को हल करता है । असली सवाल यह है: मैं अपनी समस्या कैसे हल करूं? अपने कोड को ठीक से रिफ्लेक्टर करें। रिफ्लेक्टर ताकि आप .move()या .attack()ठीक से सारthat भाग - बजाय होने .swim(), .fly(), .slide(), .hop(), .jump(), .squirm(), .phone_home(), आदि कैसे आप ठीक ढंग से refactor करते हैं? बेहतर उदाहरणों के बिना अज्ञात ... लेकिन अभी भी आम तौर पर सही उत्तर है - जब तक कि उदाहरण कोड और अधिक विवरण अन्यथा सुझाव न दें।
वर्नरसीडी 23

11
@codebreaker यदि आपकी कक्षा पदानुक्रम आपको इस तरह की स्थितियों की ओर ले जाती है, तो परिभाषा के अनुसार इसका कोई मतलब नहीं है
पैट्रिक कॉलिन्स

7
क्रोडोल की अंतिम टिप्पणी से संबंधित @codebreaker, इसे देखने का एक और तरीका है जो मदद कर सकता है। उदाहरण के लिए, moveबनाम fly/ run/ आदि के साथ, जिसे एक वाक्य में समझाया जा सकता है: आपको ऑब्जेक्ट को बताना चाहिए कि क्या करना है, कैसे नहीं करना है। एक्सेसर्स के संदर्भ में, जैसा कि आप उल्लेख करते हैं कि यहां एक टिप्पणी में वास्तविक मामला अधिक पसंद है, आपको ऑब्जेक्ट प्रश्न पूछना चाहिए, इसके राज्य का निरीक्षण नहीं करना चाहिए।
इजाकाता

9

ऐसी परिस्थिति की कल्पना करना थोड़ा मुश्किल है, जहां आपके पास चीजों का एक समूह है और वे चाहते हैं कि या तो एक सैंडविच बनाएं या एलियंस से संपर्क करें। अधिकांश मामलों में जहां आपको ऐसी कास्टिंग मिलती है, आप एक प्रकार से काम करेंगे - जैसे कि क्लैग में आप घोषणाओं के लिए नोड्स के एक सेट को फ़िल्टर करते हैं, जहाँ getAsFunction सूची में प्रत्येक नोड के लिए कुछ अलग करने के बजाय, गैर-अशक्त रिटर्न देता है।

यह हो सकता है कि आपको कार्य करने के अनुक्रम की आवश्यकता है और यह वास्तव में प्रासंगिक नहीं है कि कार्रवाई करने वाली वस्तुएं संबंधित हैं।

इसलिए सूची की बजाय Base, कार्यों की सूची पर काम करें

for (RandomAction action : actions)
   action.act(context);

कहा पे

interface RandomAction {
    void act(Context context);
} 

interface Context {
    Ingredients getRandomIngredients();
    double getFrequency();
}

यदि आप उचित हों, तो आधार ने कार्रवाई को वापस करने के लिए एक विधि को लागू किया है, या जो भी अन्य साधन आपको अपनी आधार सूची में इंस्टेंस से कार्रवाई का चयन करने की आवश्यकता है (क्योंकि आप कहते हैं कि आप बहुरूपता का उपयोग नहीं कर सकते हैं, इसलिए संभवतः कार्रवाई करने के लिए वर्ग का कार्य नहीं है, लेकिन कुछ अन्य आधारों की संपत्ति है; अन्यथा आप बस अधिनियम (संदर्भ) विधि देंगे


2
आपको लगता है कि मेरी तरह के बेतुके तरीके इस बात के अंतर के लिए तैयार किए गए हैं कि एक बार उपवर्ग ज्ञात होने पर वर्ग क्या कर सकते हैं (और यह कि उनका एक-दूसरे से कोई लेना-देना नहीं है), लेकिन मुझे लगता है कि एक Contextवस्तु का होना बस मामलों को और बदतर बनाता है। हो सकता है कि मैं सिर्फ Objectविधियों में पास हो जाऊं , उन्हें कास्टिंग करूं और उम्मीद करूं कि वे सही प्रकार हैं।
कोडब्रेकर

1
@ कोडब्रेकर आपको वास्तव में अपने प्रश्न को स्पष्ट करने की आवश्यकता है - अधिकांश प्रणालियों में वे क्या करते हैं, इसके लिए अज्ञेय के कार्यों का एक गुच्छा कॉल करने का एक वैध कारण होगा; उदाहरण के लिए एक घटना पर नियमों को संसाधित करने या एक सिमुलेशन में एक कदम करने के लिए। आमतौर पर ऐसी प्रणालियों का एक संदर्भ होता है जिसमें क्रियाएं होती हैं। यदि 'getRandomIngredients' और 'getFrequency' संबंधित नहीं हैं, तो फिर एक ही ऑब्जेक्ट में नहीं होना चाहिए, और आपको अभी तक एक अलग दृष्टिकोण की आवश्यकता है, जैसे कि क्रियाओं के अवयवों या आवृत्ति के स्रोत पर कब्जा करना।
पीट किरकम

1
आप सही हैं, और मुझे नहीं लगता कि मैं इस प्रश्न का निस्तारण कर सकता हूं। मैंने एक बुरा उदाहरण चुनकर समाप्त किया, इसलिए मैं शायद एक और पोस्ट करूंगा। GetFrequency () और getIngredients () सिर्फ प्लेसहोल्डर थे। मुझे शायद इसे और अधिक स्पष्ट करने के लिए तर्क के रूप में "..." रखना चाहिए था कि मुझे नहीं पता होगा कि वे क्या हैं।
कोडब्रेकर

यह IMO सही उत्तर है। समझते हैं कि Contextएक नया वर्ग, या यहां तक ​​कि एक इंटरफ़ेस होने की आवश्यकता नहीं है। आमतौर पर संदर्भ सिर्फ कॉल करने वाला होता है, इसलिए कॉल फॉर्म में होते हैं action.act(this)। अगर getFrequency()और getRandomIngredients()स्थिर तरीके हैं, तो आपको एक संदर्भ की भी आवश्यकता नहीं है। यह है कि मैं कैसे कहूंगा, एक नौकरी कतार, जिसे आपकी समस्या बहुत भयानक लगती है।
प्रश्न

इसके अलावा यह ज्यादातर आगंतुक पैटर्न समाधान के समान है। अंतर यह है कि आप विज़िटर को लागू कर रहे हैं :: OnSub1 () / विज़िटर :: OnSub2 () विधियाँ Sub1 :: act () / Sub2 :: एक्ट ()
प्रश्न संख्या

4

यदि आपके पास अपने उपवर्गों को एक या एक से अधिक इंटरफेस लागू करने के बारे में है जो परिभाषित करते हैं कि वे क्या कर सकते हैं? कुछ इस तरह:

interface SandwichCook
{
    public void makeASandwich(String[] ingredients);
}

interface AlienRadioSignalAwarable
{
    public void contactAliens(int frequency);

}

तब आपकी कक्षाएं इस तरह दिखेंगी:

class Sub1 extends Base implements SandwichCook
{
    public void makeASandwich(String[] ingredients)
    {
        //some code here
    }
}

class Sub2 extends Base implements AlienRadioSignalAwarable
{
    public void contactAliens(int frequency)
    {
        //some code here
    }
}

और आपका लूप बन जाएगा:

for (Base base : bases) {
    if (base instanceof SandwichCook) {
        base.makeASandwich(getRandomIngredients());
    } else if (base instanceof AlienRadioSignalAwarable) {
        base.contactAliens(getFrequency());
    }
}

इस दृष्टिकोण के दो प्रमुख लाभ:

  • कोई कास्टिंग शामिल नहीं है
  • आप प्रत्येक उपवर्ग को जितने चाहें उतने इंटरफेस लागू कर सकते हैं, जो भविष्य में होने वाले परिवर्तनों के लिए कुछ लचीलापन प्रदान करता है।

पुनश्च: इंटरफेस के नामों के लिए क्षमा करें, मैं उस विशेष क्षण में कुछ भी कूलर के बारे में नहीं सोच सकता था: डी।


3
यह वास्तव में समस्या को हल नहीं करता है। आपको अभी भी लूप के लिए कलाकारों की आवश्यकता है। (यदि आप नहीं करते हैं, तो आपको ओपी के उदाहरण में कलाकारों की आवश्यकता नहीं है)।
तैमूर

2

दृष्टिकोण उन मामलों में एक अच्छा हो सकता है जहां एक परिवार के भीतर लगभग किसी भी प्रकार या तो सीधे कुछ इंटरफ़ेस के कार्यान्वयन के रूप में उपयोग करने योग्य होगा जो कुछ मानदंड को पूरा करता है या उस इंटरफ़ेस के कार्यान्वयन को बनाने के लिए उपयोग किया जा सकता है। अंतर्निहित संग्रह प्रकार IMHO इस पैटर्न से लाभान्वित होंगे, लेकिन चूंकि वे उदाहरण के प्रयोजनों के लिए नहीं हैं इसलिए मैं एक संग्रह इंटरफ़ेस का आविष्कार करूंगा BunchOfThings<T>

के कुछ कार्यान्वयन BunchOfThingsपरस्पर हैं; कुछ नहीं हैं कई मामलों में, एक ऑब्जेक्ट फ्रेड एक ऐसी चीज को पकड़ना चाहता है जिसका वह उपयोग कर सकता है BunchOfThingsऔर यह जान सकता है कि फ्रेड के अलावा कुछ भी इसे संशोधित करने में सक्षम नहीं होगा। यह आवश्यकता दो तरीकों से संतुष्ट हो सकती है:

  1. फ्रेड जानता है कि यह एकमात्र संदर्भ रखता है BunchOfThings, और इसके बाहरी के लिए कोई बाहरी संदर्भ BunchOfThingsब्रह्मांड में कहीं भी मौजूद नहीं है। यदि किसी के पास BunchOfThingsया उसके आंतरिक का संदर्भ नहीं है, तो कोई और इसे संशोधित करने में सक्षम नहीं होगा, इसलिए बाधा संतुष्ट होगी।

  2. न तो और न BunchOfThingsही इसके किसी भी इंटर्नल से जिसके बाहर संदर्भ मौजूद हैं, किसी भी माध्यम से संशोधित किया जा सकता है। यदि पूरी तरह से कोई भी संशोधित नहीं कर सकता है BunchOfThings, तो बाधा संतुष्ट हो जाएगी।

बाधा को संतुष्ट करने का एक तरीका किसी भी प्राप्त वस्तु को बिना शर्त कॉपी करना होगा (किसी भी नेस्टेड घटकों को पुन: प्रसंस्करण करना)। एक और परीक्षण होगा कि क्या प्राप्त वस्तु अपरिवर्तनीयता का वादा करती है और यदि नहीं, तो उसकी एक प्रति बनाएं और किसी भी नेस्टेड घटकों के साथ ऐसा ही करें। एक विकल्प, जो पहले की तुलना में दूसरे और तेजी से क्लीनर होने के लिए उपयुक्त है, एक AsImmutableविधि की पेशकश करने के लिए है जो किसी वस्तु को खुद की एक अपरिवर्तनीय प्रतिलिपि बनाने के लिए कहता है ( AsImmutableइसका समर्थन करने वाले किसी भी नेस्टेड घटकों पर)।

संबंधित तरीकों के लिए भी प्रदान किया जा सकता है asDetached(उपयोग के लिए जब कोड एक वस्तु प्राप्त करता है और यह नहीं जानता कि क्या वह इसे म्यूट करना चाहेगा, इस स्थिति में एक उत्परिवर्तित वस्तु को एक नई उत्परिवर्तित वस्तु से बदला जाना चाहिए, लेकिन एक अपरिवर्तनीय वस्तु रखी जा सकती है as-is), asMutable(उन मामलों में जहां कोई वस्तु जानती है कि वह पहले से वापस लौटी हुई वस्तु को धारण करेगी asDetached, अर्थात या तो एक उत्परिवर्तित वस्तु का एक साझा संदर्भ या एक उत्परिवर्ती के लिए एक साझा संदर्भ), और asNewMutable(उन मामलों में जहां कोड एक बाहरी प्राप्त करता है संदर्भ और यह जानता है कि इसमें डेटा की एक प्रति म्यूट करना चाहता है - यदि आने वाला डेटा उत्परिवर्तनीय है, तो एक अपरिवर्तनीय प्रतिलिपि बनाने से शुरू करने का कोई कारण नहीं है जो तुरंत एक उत्परिवर्ती प्रतिलिपि बनाने के लिए उपयोग किया जा रहा है और फिर छोड़ दिया गया है)।

ध्यान दें कि जब asXXविधियाँ थोड़ा भिन्न प्रकार की हो सकती हैं, तो उनकी वास्तविक भूमिका यह सुनिश्चित करना है कि लौटी हुई वस्तुएँ कार्यक्रम की जरूरतों को पूरा करेंगी।


0

इस मुद्दे की अनदेखी करना कि आपके पास एक अच्छा डिज़ाइन है या नहीं, और यह मानकर कि यह अच्छा है या कम से कम स्वीकार्य है, मैं उपवर्गों की क्षमताओं पर विचार करना चाहूंगा, न कि प्रकार।

इसलिए, या तो:


सैंडविच और एलियंस के अस्तित्व के कुछ ज्ञान को आधार वर्ग में ले जाएँ, भले ही आपको पता हो कि आधार वर्ग के कुछ उदाहरण ऐसा नहीं कर सकते हैं। अपवादों को फेंकने के लिए इसे आधार वर्ग में लागू करें और कोड को इसमें बदलें:

if (base.canMakeASandwich()) {
    base.makeASandwich(getRandomIngredients());
    // ... etc.
} else { // can't make sandwiches, must be able to contact aliens
    base.contactAliens(getFrequency());
    // ... etc.
}

फिर एक या दोनों उपवर्ग ओवरराइड करते हैं canMakeASandwich(), और केवल एक ही प्रत्येक को लागू करता है makeASandwich()और contactAliens()


एक प्रकार की क्षमता क्या है, यह पता लगाने के लिए, कंक्रीट उपवर्गों में इंटरफेस का उपयोग करें। बेस क्लास को अकेला छोड़ दें, और कोड को इसमें बदलें:

if (base instanceof SandwichMaker) {
    ((SandwichMaker)base).makeASandwich(getRandomIngredients());
    // ... etc.
} else { // can't make sandwiches, must be able to contact aliens
    ((AlienContacter)base).contactAliens(getFrequency());
    // ... etc.
}

या संभवतः (और इस विकल्प को अनदेखा करने के लिए स्वतंत्र महसूस करें यदि यह आपकी शैली के अनुरूप नहीं है, या उस मामले के लिए कोई जावा शैली जिसे आप समझदार समझेंगे):

try {
    ((SandwichMaker)base).makeASandwich(getRandomIngredients());
} catch (ClassCastException e) {
    ((AlienContacter)base).contactAliens(getFrequency());
}

व्यक्तिगत रूप से मुझे आमतौर पर आधे-प्रत्याशित अपवाद को पकड़ने की बाद की शैली पसंद नहीं है, क्योंकि अनुचित रूप ClassCastExceptionसे getRandomIngredientsया बाहर आने वाले वाईएमएमवी को पकड़ने के जोखिम के कारण makeASandwich


2
ClassCastException को पकड़ना केवल ew है। यह उदाहरण का पूरा उद्देश्य है।
रेडियोडफ

@ रोडीफ़: सच, लेकिन इसका एक उद्देश्य <पकड़ने से बचना है ArrayIndexOutOfBoundsException, और कुछ लोग ऐसा करने का निर्णय लेते हैं। स्वाद के लिए कोई लेखांकन नहीं ;-) मूल रूप से मेरी "समस्या" यहां मैं पायथन में काम कर रहा हूं, जहां आमतौर पर पसंदीदा शैली है कि किसी भी अपवाद को पकड़ना अग्रिम में परीक्षण के लिए बेहतर है कि क्या अपवाद होगा, कोई फर्क नहीं पड़ता कि अग्रिम परीक्षण कितना सरल होगा । शायद संभावना सिर्फ जावा में ध्यान देने योग्य नहीं है।
स्टीव जेसोप

@SteveJessop »यह अनुमति के लिए माफी से अधिक आसान है« नारा है?)
थॉमस

0

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

  1. हम सभी मामलों को आधार वर्ग में शामिल कर सकते हैं।
  2. किसी भी विदेशी व्युत्पन्न वर्ग को एक नया मामला नहीं जोड़ना होगा।
  3. हम उस नीति के साथ रह सकते हैं जिसके लिए आधार वर्ग के नियंत्रण में कॉल करना है।
  4. लेकिन हम अपने विज्ञान से जानते हैं कि किसी वर्ग के आधार वर्ग में नहीं आने वाली पद्धति के लिए क्या करना है, यह नीति व्युत्पन्न वर्ग की है, न कि आधार वर्ग की।

यदि 4 है तो हमारे पास है: 5. व्युत्पन्न वर्ग की नीति हमेशा आधार वर्ग की नीति के समान राजनीतिक नियंत्रण में होती है।

2 और 5 दोनों का सीधा मतलब है कि हम सभी व्युत्पन्न वर्गों की गणना कर सकते हैं, जिसका अर्थ है कि बाहर के किसी भी वर्ग को माना नहीं जाता है।

लेकिन यहाँ बात है। यदि वे आपके सभी हैं, तो यदि आप एक एब्सट्रैक्शन के साथ बदल सकते हैं जो एक आभासी विधि कॉल है (भले ही यह एक बकवास है) और अगर और स्व-कलाकारों से छुटकारा पाएं। इसलिए, यह मत करो। एक बेहतर डिजाइन उपलब्ध है।


-1

बेस क्लास में एक एब्सट्रैक्ट मेथड डालें जो सब 1 में एक काम करता है और दूसरी चीज सब 2 में, और उस एब्सट्रैक्ट मेथड को अपने मिक्स्ड लूप में कहते हैं।

class Sub1 : Base {
    @Override void doAThing() {
        this.makeASandwich(getRandomIngredients());
    }
}

class Sub2 : Base {
    @Override void doAThing() {
        this.contactAliens(getFrequency());
    }
}

for (Base base : bases) {
    base.doAThing();
}

ध्यान दें कि यह getRandomIngredients और getFrequency के आधार पर अन्य परिवर्तनों की आवश्यकता हो सकती है। लेकिन, ईमानदारी से, क्लास के अंदर जो एलियंस से संपर्क करता है, वह वैसे भी getFrequency को परिभाषित करने के लिए एक बेहतर जगह है।

संयोग से, आपके asSub1 और asSub2 तरीके निश्चित रूप से बुरे अभ्यास हैं। यदि आप ऐसा करने जा रहे हैं, तो इसे कास्टिंग के द्वारा करें। इन तरीकों से कुछ भी नहीं है जो आपको देता है कि कास्टिंग नहीं करता है।


2
आपका उत्तर बताता है कि आपने इस प्रश्न को पूरी तरह से नहीं पढ़ा है: बहुरूपता सिर्फ यहाँ नहीं कटेगी। Sub1 और Sub2 में से प्रत्येक पर कई विधियाँ हैं, ये केवल उदाहरण हैं, और "doAThing" का वास्तव में कोई मतलब नहीं है। यह मेरे द्वारा किए जा रहे किसी भी चीज़ के अनुरूप नहीं है। इसके अतिरिक्त, आपके अंतिम वाक्य के रूप में: गारंटी प्रकार की सुरक्षा के बारे में कैसे?
कोडब्रेकर

@ कोडब्रेकर - यह ठीक उसी तरह से मेल खाता है जैसा आप उदाहरण में लूप में कर रहे हैं। यदि इसका कोई अर्थ नहीं है, तो लूप न लिखें। अगर यह एक विधि के रूप में समझ में नहीं आता है तो यह लूप बॉडी के रूप में समझ में नहीं आता है।
रैंडम 832

1
और आपके तरीके "गारंटी" प्रकार की सुरक्षा उसी तरह से कास्टिंग करते हैं: यदि वस्तु गलत प्रकार है, तो अपवाद को फेंकने से।
रैंडम 832

मुझे लगता है कि मैंने एक बुरा उदाहरण उठाया। समस्या यह है कि उपवर्गों में अधिकांश विधियां ऐसी होती हैं जो उत्परिवर्ती तरीकों की तुलना में गेटर्स की तरह होती हैं। ये वर्ग चीजें खुद नहीं करते हैं, सिर्फ डेटा प्रदान करते हैं, ज्यादातर। बहुरूपता वहाँ मेरी मदद नहीं करेगा। मैं उत्पादकों के साथ काम कर रहा हूं, उपभोक्ताओं के साथ नहीं। इसके अलावा: एक अपवाद फेंकना एक रनटाइम गारंटी है, जो संकलन-समय के रूप में अच्छा नहीं है।
कोडब्रेकर

1
मेरा कहना है कि आपका कोड केवल रनटाइम गारंटी प्रदान करता है। कुछ भी नहीं है किसी को रोकने के लिए जाँच करने से रोक रहा है।
रैंडम 832

-2

आप दोनों को धक्का सकता है makeASandwichऔर contactAliensआधार वर्ग में और फिर उन्हें डमी कार्यान्वयन के साथ लागू करते हैं। चूंकि रिटर्न प्रकार / तर्क एक सामान्य आधार वर्ग के लिए हल नहीं किए जा सकते हैं।

class Sub1 extends Base{
    Sandwich makeASandwich(Ingredients i){
        //Normal implementation
    }
    boolean contactAliens(Frequency onFrequency){
        return false;
    }
 }
class Sub2 extends Base{
    Sandwich makeASandwich(Ingredients i){
        return null;
    }
    boolean contactAliens(Frequency onFrequency){
       // normal implementation
    }
 }

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


1
मैंने ऐसा करने के बारे में सोचा था, लेकिन यह लिस्कोव सबस्टीट्यूशन सिद्धांत का उल्लंघन करता है, और जब आप "सब 1" टाइप करते हैं, तो इसके साथ काम करना उतना अच्छा नहीं है। और स्वत: पूर्ण आता है, आप मेकेंडविच देखते हैं लेकिन संपर्क नहीं करते हैं।
कोडब्रेकर

-5

आप जो कर रहे हैं वह पूरी तरह से वैध है। उन नायरों पर ध्यान न दें जो केवल हठधर्मिता दोहराते हैं क्योंकि वे इसे कुछ पुस्तकों में पढ़ते हैं। डोगमा का इंजीनियरिंग में कोई स्थान नहीं है।

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

उदाहरण के लिए लें java.lang.reflect.Member, जो आधार है java.lang.reflect.Fieldऔरjava.lang.reflect.Method । (वास्तविक पदानुक्रम इसके मुकाबले थोड़ा अधिक जटिल है, लेकिन यह अप्रासंगिक है।) फ़ील्ड और विधियां बहुत अलग-अलग जानवर हैं: एक का मूल्य है जिसे आप प्राप्त कर सकते हैं या सेट कर सकते हैं, जबकि दूसरे के पास ऐसी कोई चीज नहीं है, लेकिन इसके साथ इसे लागू किया जा सकता है। कई मापदंडों और यह एक मूल्य वापस कर सकते हैं। तो, फ़ील्ड और विधियाँ दोनों सदस्य हैं, लेकिन आप जिन चीज़ों के साथ कर सकते हैं वे एक दूसरे से सैंडविच बनाने बनाम एलियंस से संपर्क करने के बारे में अलग हैं।

अब, जब कोड लिखना जो प्रतिबिंब का उपयोग करता है, तो Memberहमारे हाथों में अक्सर होता है, और हम जानते हैं कि यह या तो ए हैMethod या एक है Field, (या, शायद ही कभी, कुछ और), और फिर भी हमें instanceofठीक से पता लगाने के लिए सभी थकाऊ करना होगा यह क्या है और फिर हमें इसे एक उचित संदर्भ प्राप्त करने के लिए डालना होगा। (और यह केवल थकाऊ नहीं है, लेकिन यह भी बहुत अच्छा प्रदर्शन नहीं करता है।) Methodवर्ग बहुत आसानी से उस पैटर्न को लागू कर सकता है जिसे आप वर्णन कर रहे हैं, इस प्रकार हजारों प्रोग्रामर का जीवन आसान हो जाता है।

बेशक, यह तकनीक केवल बारीकी से युग्मित वर्गों की छोटी, अच्छी तरह से परिभाषित पदानुक्रमों में व्यवहार्य है, जो आपके पास (और हमेशा होगी) स्रोत-स्तर का नियंत्रण: आप ऐसा नहीं करना चाहते हैं यदि आपकी कक्षा पदानुक्रम है। बेस क्लास को रिफलेक्टर करने के लिए स्वतंत्र नहीं होने वाले लोगों द्वारा बढ़ाया जा सकता है।

यहाँ बताया गया है कि मैंने जो कुछ किया है, उससे अलग है:

  • बेस क्लास asDerivedClass()तरीकों के पूरे परिवार के लिए एक डिफ़ॉल्ट कार्यान्वयन प्रदान करता है , उनमें से प्रत्येक में वापसी होती है null

  • प्रत्येक व्युत्पन्न वर्ग केवल asDerivedClass()तरीकों में से एक को ओवरराइड करता है , thisइसके बजाय वापस लौटता है null। यह बाकी में से कोई भी ओवरराइड नहीं करता है और न ही यह उनके बारे में कुछ जानना चाहता है। तो, कोई IllegalStateExceptionभी फेंक दिया जाता है।

  • बेस क्लास भी तरीकों finalके पूरे isDerivedClass()परिवार के लिए कार्यान्वयन प्रदान करता है , निम्नानुसार कोडित किया गया है:return asDerivedClass() != null; इस तरह, व्युत्पन्न वर्गों द्वारा ओवरराइड करने की आवश्यकता वाले तरीकों की संख्या कम से कम हो जाती है।

  • मैं @Deprecatedइस तंत्र में उपयोग नहीं कर रहा हूं क्योंकि मैंने इसके बारे में नहीं सोचा था। अब जब आपने मुझे विचार दिया है, तो मैं इसे उपयोग करने के लिए डालूंगा, धन्यवाद!

C # में asकीवर्ड के उपयोग से संबंधित एक संबंधित तंत्र है । C # में आप कह सकते हैं DerivedClass derivedInstance = baseInstance as DerivedClassऔर DerivedClassयदि आप अपने baseInstanceवर्ग के थे, या nullयदि ऐसा नहीं था , तो आपको एक संदर्भ मिलेगा । यह (सैद्धांतिक रूप से) isकास्ट की तुलना में बेहतर प्रदर्शन करता है , ( isयह शाब्दिक रूप से C # कीवर्ड के लिए बेहतर नाम है instanceof), लेकिन हमारे द्वारा तैयार किए गए कस्टम तंत्र और भी बेहतर प्रदर्शन करते हैं: instanceofजावा की -एंड-कास्ट संचालन, साथ ही साथ asसी # के ऑपरेटर के रूप में हमारे कस्टम दृष्टिकोण के एकल आभासी विधि कॉल के रूप में तेजी से प्रदर्शन नहीं करते हैं।

मैंने इस प्रस्ताव को रखा कि इस तकनीक को एक पैटर्न घोषित किया जाना चाहिए और इसके लिए एक अच्छा नाम ढूंढा जाना चाहिए।

जी, डाउनवोट्स के लिए धन्यवाद!

टिप्पणियों को पढ़ने की परेशानी से बचाने के लिए विवाद का सारांश:

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

हालांकि, ऊपर दिए गए उदाहरण में मैं पहले से ही है पता चला है कि जावा रनटाइम के रचनाकारों में तथ्य के लिए ठीक इस तरह की डिजाइन का चयन किया था java.lang.reflect.Member, Field, Methodपदानुक्रम, और टिप्पणियों में नीचे मैं भी पता चलता है कि सी # क्रम के रचनाकारों स्वतंत्र रूप से एक पर पहुंचे बराबर के लिए डिजाइन System.Reflection.MemberInfo, FieldInfo,MethodInfo पदानुक्रम। तो, ये दो अलग-अलग वास्तविक परिदृश्य हैं जो हर किसी के नाक के ठीक नीचे बैठे हैं और जिनके पास बिल्कुल ऐसे डिजाइनों का उपयोग करके व्यावहारिक रूप से व्यावहारिक समाधान हैं।

यही सभी निम्नलिखित टिप्पणियों को उबालते हैं। स्वयं-कास्टिंग तंत्र का उल्लेख शायद ही किया जाता है।


13
यदि आपने खुद को एक बॉक्स में डिज़ाइन किया है तो यह वैध है, निश्चित रूप से। इस प्रकार के बहुत सारे प्रश्नों के साथ यही समस्या है। लोग डिजाइन के अन्य क्षेत्रों में निर्णय लेते हैं जो इस प्रकार के हैकिश समाधानों को मजबूर करते हैं जब वास्तविक समाधान यह पता लगाने के लिए होता है कि आप इस स्थिति में पहले स्थान पर क्यों समाप्त हुए। एक सभ्य डिजाइन आपको यहां नहीं मिलेगा। मैं वर्तमान में 200K SLOC एप्लिकेशन देख रहा हूं और मैं कहीं भी नहीं देख रहा हूं कि हमें ऐसा करने की आवश्यकता है। तो यह सिर्फ कुछ लोगों को एक किताब में पढ़ा नहीं है। इस प्रकार की स्थितियों में नहीं मिलना केवल अच्छी प्रथाओं का पालन करने का अंतिम परिणाम है।
डंक

3
@ डंक मैंने केवल यह दिखाया है कि java.lang.reflection.Memberपदानुक्रम में इस तरह के तंत्र की आवश्यकता मौजूद है। जावा रनटाइम के रचनाकारों को इस प्रकार की स्थिति में मिला, मुझे नहीं लगता कि आप कहेंगे कि उन्होंने खुद को एक बॉक्स में डिज़ाइन किया है, या किसी प्रकार की सर्वोत्तम प्रथाओं का पालन नहीं करने के लिए उन्हें दोष देते हैं? इसलिए, मैं यहां यह बात कर रहा हूं कि जब आपके पास इस प्रकार की स्थिति है तो यह तंत्र चीजों को बेहतर बनाने का एक अच्छा तरीका है।
माइक नाकिस

6
@MikeNakis जावा के रचनाकारों ने बहुत सारे बुरे निर्णय लिए हैं, ताकि अकेले ज्यादा कुछ न कहे। किसी भी दर पर, आगंतुक का उपयोग करना तदर्थ परीक्षण-तब-कास्ट करने से बेहतर होगा।
डोभाल

3
इसके अतिरिक्त, उदाहरण त्रुटिपूर्ण है। एक Memberइंटरफ़ेस है, लेकिन यह केवल एक्सेस क्वालिफायर की जांच करने का काम करता है। आमतौर पर, आपको विधियों या गुणों की एक सूची मिलती है और इसका उपयोग सीधे (उन्हें मिश्रण किए बिना, इसलिए कोई List<Member>प्रकट नहीं होता है), इसलिए आपको कास्ट करने की आवश्यकता नहीं है। ध्यान दें कि Classएक getMembers()विधि प्रदान नहीं करता है । बेशक, एक clueless प्रोग्रामर बना सकता है List<Member>, लेकिन है कि यह एक बनाने के रूप में छोटे भावना के रूप में होगा List<Object>और कुछ जोड़ने Integerया Stringइसे करने के लिए।
एसजुआन76

5
@ मायकेनकिस "बहुत से लोग इसे करते हैं" और "प्रसिद्ध व्यक्ति इसे करता है" वास्तविक तर्क नहीं हैं। एंटीपार्टर्न दोनों बुरे विचार हैं और व्यापक रूप से परिभाषा द्वारा उपयोग किए जाते हैं, फिर भी वे बहाने उनके उपयोग को सही ठहराएंगे। कुछ डिज़ाइन या किसी अन्य का उपयोग करने के लिए वास्तविक कारण दें। तदर्थ कास्टिंग के साथ मेरा मुद्दा यह है कि आपके एपीआई के प्रत्येक उपयोगकर्ता को हर बार कास्टिंग करने का अधिकार प्राप्त होता है। एक आगंतुक को केवल एक बार सही होना चाहिए। कुछ तरीकों के पीछे कास्टिंग को छिपाना और nullएक अपवाद के बजाय वापस आना बेहतर नहीं है। बाकी सभी समान हैं, समान काम करते हुए आगंतुक सुरक्षित है।
डोभाल
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.