आप एक बहुरूपी वर्ग के लिए GUI कैसे बनाते हैं?


17

मान लीजिए कि मुझे टेस्ट बिल्डर मिल गया है, ताकि शिक्षक परीक्षण के लिए प्रश्नों का एक गुच्छा बना सकें।

हालांकि, सभी प्रश्न समान नहीं हैं: आपके पास बहुविकल्पी, टेक्स्ट बॉक्स, मिलान, और इसी तरह से हैं। इनमें से प्रत्येक प्रश्न प्रकार को विभिन्न प्रकार के डेटा को संग्रहीत करने की आवश्यकता होती है, और निर्माता और परीक्षार्थी दोनों के लिए अलग-अलग GUI की आवश्यकता होती है।

मैं दो चीजों से बचना चाहता हूं:

  1. चेक टाइप करें या कास्टिंग करें
  2. मेरे डेटा कोड में GUI से संबंधित कुछ भी।

अपने प्रारंभिक प्रयास में, मैं निम्नलिखित कक्षाओं के साथ समाप्त होता हूं:

class Test{
    List<Question> questions;
}
interface Question { }
class MultipleChoice implements Question {}
class TextBox implements Question {}

हालांकि, जब मैं परीक्षण प्रदर्शित करने के लिए जाता हूं, तो मैं अनिवार्य रूप से कोड जैसे समाप्त कर दूंगा:

for (Question question: questions){
    if (question instanceof MultipleChoice){
        display.add(new MultipleChoiceViewer());
    } 
    //etc
}

यह वास्तव में एक आम समस्या की तरह लगता है। क्या कुछ डिज़ाइन पैटर्न है जो मुझे ऊपर सूचीबद्ध वस्तुओं से परहेज करते हुए बहुरूपी प्रश्न करने की अनुमति देता है? या क्या बहुरूपता पहली जगह में गलत विचार है?


6
यह उन चीजों के बारे में पूछने के लिए एक बुरा विचार नहीं है जिनके साथ आपकी समस्याएं हैं, लेकिन मेरे लिए यह सवाल बहुत व्यापक / अस्पष्ट है और आखिर में आप सवाल पूछ रहे हैं ...
kayess

1
सामान्य तौर पर, मैं टाइप चेक / टाइप कास्टिंग से बचने की कोशिश करता हूं क्योंकि यह आम तौर पर कम संकलन-समय की जाँच की ओर जाता है और मूल रूप से इसका उपयोग करने के बजाय बहुरूपता "के आसपास" काम कर रहा है। मैं मूल रूप से उनके विरोध में नहीं हूं, लेकिन उनके बिना समाधान खोजने की कोशिश करता हूं।
नाथन मेरिल

1
आप जो खोज रहे हैं, वह मूल रूप से साधारण टेम्पलेट का वर्णन करने के लिए एक डीएसएल है , न कि पदानुक्रमित ऑब्जेक्ट मॉडल।
user1643723

2
@NathanMerrill "मैं निश्चित रूप से बहुरूपता चाहता हूं", - क्या यह दूसरा तरीका नहीं होना चाहिए? क्या आप अपने वास्तविक लक्ष्य को प्राप्त करेंगे या "पॉलीमोफिज़्म का उपयोग करेंगे"? आईएमओ, पॉलीमोफिज़्म जटिल एपीआई के निर्माण और मॉडलिंग व्यवहार के लिए अच्छी तरह से अनुकूल है। यह मॉडलिंग डेटा के लिए कम अनुकूल है (जो कि आप वर्तमान में कर रहे हैं)।
user1643723

1
@NathanMerrill "प्रत्येक टाइमब्लॉक एक एक्शन निष्पादित करता है, या अन्य टाइमब्लॉक शामिल करता है और उन्हें निष्पादित करता है, या उपयोगकर्ता शीघ्रता से अनुरोध करता है", - यह जानकारी अत्यधिक मूल्यवान है, मेरा सुझाव है, कि आप इसे प्रश्न में जोड़ते हैं।
user1643723

जवाबों:


15

आप एक विज़िटर पैटर्न का उपयोग कर सकते हैं:

interface QuestionVisitor {
    void multipleChoice(MultipleChoice);
    void textBox(TextBox);
    ...
}

interface Question {
    void visit(QuestionVisitor);
}

class MultipleChoice implements Question {

    void visit(QuestionVisitor visitor) {
        visitor.multipleChoice(this);
    }
}

एक अन्य विकल्प भेदभाव रहित संघ है। यह आपकी भाषा पर बहुत निर्भर करेगा। यदि आपकी भाषा इसका समर्थन करती है, तो यह बहुत बेहतर है, लेकिन कई लोकप्रिय भाषाएँ नहीं हैं।


2
हम्म .... यह एक भयानक विकल्प नहीं है, हालांकि प्रश्नावली इंटरफ़ेस को प्रत्येक बार एक अलग प्रकार का प्रश्न जोड़ने की आवश्यकता होगी, जो सुपर स्केलेबल नहीं है।
नाथन मेरिल

3
@NathanMerrill, मुझे नहीं लगता कि यह वास्तव में आपकी स्केलेबिलिटी को बहुत बदल देता है। हां, आपको प्रश्न विधि के प्रत्येक उदाहरण में नई पद्धति को लागू करना होगा। लेकिन यह कोड आपको नए प्रश्न प्रकार के लिए GUI को संभालने के लिए किसी भी मामले में लिखना होगा। मुझे नहीं लगता कि यह वास्तव में इतना कोड जोड़ता है जो आपको अन्यथा सही नहीं करना होगा, लेकिन यह अनुपलब्ध कोड को एक संकलन त्रुटि में बदल देता है।
विंस्टन एवर्ट

4
सच। हालाँकि, अगर मैं कभी किसी को अपना प्रश्न प्रकार + रेंडरर बनाने की अनुमति देना चाहता था (जो मैं नहीं करता), तो मुझे नहीं लगता कि यह संभव होगा।
नाथन मेरिल

2
@NathanMerrill, यह सच है। यह दृष्टिकोण मानता है कि केवल एक कोड आधार प्रश्न प्रकारों को परिभाषित कर रहा है।
विंस्टन एवर्ट

4
@InstonEwert यह विज़िटर पैटर्न का अच्छा उपयोग है। लेकिन आपका कार्यान्वयन पैटर्न के अनुसार काफी नहीं है। आमतौर पर आगंतुक के तरीकों का नाम प्रकारों के नाम पर नहीं रखा जाता है, उनका आमतौर पर एक ही नाम होता है और केवल मापदंडों के प्रकार (पैरामीटर ओवरलोडिंग) में भिन्न होते हैं; सामान्य नाम है visit(आगंतुक का दौरा)। साथ ही जिन वस्तुओं का दौरा किया जा रहा है उनमें विधि को आमतौर पर कहा जाता है accept(Visitor)(ऑब्जेक्ट एक आगंतुक को स्वीकार करता है)। देखें oodesign.com/visitor-pattern.html
विक्टर सीफ़र्ट

2

C # / WPF में (और, मैं कल्पना करता हूं, अन्य यूआई-केंद्रित डिज़ाइन भाषाओं में), हमारे पास डेटाटेम्पलेट्स हैं । डेटा टेम्प्लेट को परिभाषित करके, आप उस डेटा को प्रदर्शित करने के लिए विशेष रूप से बनाए गए एक प्रकार के "डेटा ऑब्जेक्ट" और एक विशेष "यूआई टेम्पलेट" के बीच एक जुड़ाव बनाते हैं।

एक बार जब आप UI को एक विशिष्ट प्रकार की ऑब्जेक्ट लोड करने के लिए निर्देश प्रदान करते हैं, तो यह देखेगा कि ऑब्जेक्ट के लिए कोई डेटा टेम्प्लेट परिभाषित हैं या नहीं।


यह समस्या को XML में ले जाना प्रतीत होता है जहां आप पहली बार में सभी सख्त टाइपिंग खो देते हैं।
नाथन मेरिल

मुझे यकीन नहीं है कि अगर आप कह रहे हैं कि यह अच्छी बात है या बुरी बात है। एक ओर, हम समस्या को आगे बढ़ा रहे हैं। दूसरी ओर, यह स्वर्ग में किए गए मैच की तरह लगता है।
BTownTKD

2

यदि हर उत्तर को एक स्ट्रिंग के रूप में एन्कोड किया जा सकता है, तो आप ऐसा कर सकते हैं:

interface Question {
    int score(String answer);
    void display(String answer);
    void displayGraded(String answer);
}

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

class MultipleChoice implements Question {
    MultipleChoiceView mcv;
    String question;
    String answerKey;
    String[] choices;

    MultipleChoice(
            MultipleChoiceView mcv, 
            String question, 
            String answerKey, 
            String... choices
    ) {
        this.mcv = mcv;
        this.question = question;
        this.answerKey = answerKey;
        this.choices = choices;
    }

    int score(String answer) {
        return answer.equals(answerKey); //Or whatever scoring logic
    }

    void display(String answer) {
        mcv.display(question, choices, answer);            
    }

    void displayGraded(String answer) {
        mcv.displayGraded(
            question, 
            answerKey, 
            choices, 
            answer, 
            score(answer)
        );            
    }
}

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

आउटपुट को अलग करके display()और displayGraded()दृश्य को स्वैप करने की आवश्यकता नहीं है और मापदंडों पर कोई ब्रांचिंग करने की आवश्यकता नहीं है। हालाँकि, प्रत्येक दृश्य प्रदर्शित करने के लिए उतने ही तर्क का पुन: उपयोग करने के लिए स्वतंत्र है जितना कि प्रदर्शित करते समय। इस कोड में लीक करने की जरूरत नहीं है जो कुछ भी करने के लिए तैयार है।

यदि, हालांकि, आप एक और अधिक गतिशील नियंत्रण चाहते हैं कि कैसे एक प्रश्न प्रदर्शित किया जाए तो आप यह कर सकते हैं:

interface Question {
    int score(String answer);
    void display(MultipleChoiceView mcv, String answer);
}

और इस

class MultipleChoice implements Question {
    String question;
    String answerKey;
    String[] choices;

    MultipleChoice(
            String question, 
            String answerKey, 
            String... choices
    ) {
        this.question = question;
        this.answerKey = answerKey;
        this.choices = choices;
    }

    int score(String answer) {
        return answer.equals(answerKey); //Or whatever scoring logic
    }

    void display(MultipleChoiceView mcv, String answer) {
        mcv.display(
            question, 
            answerKey, 
            choices, 
            answer, 
            score(answer)
        );            
    }
}

इसका दोष यह है कि इसके लिए ऐसे विचारों की आवश्यकता होती है, जो उनकी आवश्यकता नहीं होने पर उन पर निर्भर होने score()या answerKeyउन पर निर्भर होने का प्रदर्शन नहीं करते हैं। लेकिन इसका मतलब है कि आपको प्रत्येक प्रकार के देखने के लिए परीक्षण प्रश्नों का पुनर्निर्माण नहीं करना है।


तो यह प्रश्न में GUI कोड डालता है। आपका "डिस्प्ले" और "डिस्प्लेग्रेडेड" खुलासा कर रहा है: हर प्रकार के "डिस्प्ले" के लिए, मुझे एक और फ़ंक्शन करना होगा।
नाथन मेरिल

काफी नहीं, यह एक ऐसे दृष्टिकोण का संदर्भ देता है जो बहुरूपी है। यह एक GUI, एक वेब पेज, एक पीडीएफ, जो भी हो। यह एक आउटपुट पोर्ट है जिसे लेआउट मुक्त सामग्री भेजा जा रहा है।
candied_orange

@NathanMerrill कृपया संपादित करें
candied_orange

नया इंटरफ़ेस काम नहीं करता है: आप "प्रश्न" इंटरफ़ेस के अंदर "MultipleChoiceView" डाल रहे हैं। आप व्यूअर को कंस्ट्रक्टर में डाल सकते हैं , लेकिन जब आप ऑब्जेक्ट बनाते हैं तो अधिकांश समय आपको पता नहीं होता (या देखभाल) होता है कि कौन सा दर्शक होगा। (यह एक आलसी फ़ंक्शन / फैक्टरी का उपयोग करके हल किया जा सकता है लेकिन उस कारखाने में इंजेक्शन लगाने के पीछे का तर्क गड़बड़ हो सकता है)
नाथन मेरिल

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

1

मेरी राय में, यदि आपको ऐसी सामान्य सुविधा की आवश्यकता है, तो मुझे कोड में सामान के बीच युग्मन कम करना होगा। मैं इस प्रश्न को परिभाषित करने की कोशिश करूंगा कि मैं जितना अधिक सामान्य हो सकता हूं, और उसके बाद मैं रेंडरर ऑब्जेक्ट्स के लिए अलग-अलग कक्षाएं बनाऊंगा। कृपया, नीचे दिए गए उदाहरण देखें:

///Questions package

class Test {
  IList<Question> questions;
}

class Question {
  String Type;   //example; could be another type
  IList<QuestionInfo> Info;  //Simple array of key/value information
}

फिर, रेंडरिंग भाग के लिए, मैंने प्रश्न ऑब्जेक्ट के भीतर डेटा पर एक साधारण चेक लागू करके टाइप चेकिंग को हटा दिया। नीचे दिया गया कोड दो चीजों को पूरा करने की कोशिश करता है: (i) प्रकार की जाँच से बचें और प्रश्न वर्ग घटाव को हटाकर "L" सिद्धांत (SOLID में Liskov प्रतिस्थापन) के उल्लंघन से बचें; और (ii) नीचे दिए गए कोर रेंडरिंग कोड को कभी न बदलकर, कोड को एक्स्टेंसिबल बना दें, केवल अधिक प्रश्नदृश्य कार्यान्वयन और इसके उदाहरणों को जोड़कर (यह वास्तव में SOLID में "O" सिद्धांत है - विस्तार के लिए खुला और संशोधन के लिए बंद)।

///GUI package

interface QuestionView {
  Boolean SupportsQuestion(Question question);
  View CreateView(Question question);
}

class MultipleChoiceQuestionView : QuestionView {
  Boolean SupportsQuestion(Question question){
    return question.Type == "multiple_coice";
  }

  //...more implementation
}
class TextBoxQuestionView : QuestionView { ... }
//...more views

//Assuming you have an array of QuestionView pre-configured
//with all currently available types of questions
for (Question question : questions) {
  for (QuestionView view : questionViews) {
    if (view.SupportsQuestion(question)) {
        display.add(view.CreateView(question));
    }
  }
}

क्या होता है जब MultipleChoiceQuestionView फ़ील्ड में कई MultiChoice.choices तक पहुँचने की कोशिश करता है? इसके लिए कास्ट चाहिए। यकीन है, अगर हम मान लेते हैं कि यह प्रश्न अद्वितीय है और कोड समझदार है, तो यह एक बहुत ही सुरक्षित कलाकार है, लेकिन यह अभी भी एक कलाकार है: पी
नाथन मेरिल

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

मेरे उदाहरण में, मैंने आपके जीयूआई और विशिष्ट प्रश्न क्लैस में मजबूत टाइप किए गए गुणों के बीच युग्मन को कम करना चुना; इसके बजाय मैं उन गुणों को जेनेरिक गुणों द्वारा प्रतिस्थापित करता हूं, जिनके लिए GUI को स्ट्रिंग कुंजी या कुछ और (ढीली युग्मन) द्वारा एक्सेस करने की आवश्यकता होगी। यह एक ट्रेडऑफ़ है, शायद यह ढीला युग्मन आपके परिदृश्य में वांछित नहीं है।
एमर्सन कार्डसो

1

एक कारखाना ऐसा करने में सक्षम होना चाहिए। नक्शा स्विच स्टेटमेंट की जगह लेता है, जिसे क्वेश्चन व्यू के साथ क्वेश्चन (जो कि व्यू के बारे में कुछ भी नहीं पता है) को पेयर करने के लिए पूरी तरह से जरूरी है।

interface QuestionView<T : Question>
{
    view();
}

class MultipleChoiceView implements QuestionView<MultipleChoiceQuestion>
{
    MultipleChoiceQuestion question;
    view();
}
...

class QuestionViewFactory
{
    Map<K : Question, V : QuestionView<K>> map;

    register<K : Question, V : QuestionView<K>>();
    getView(Question)
}

इसके साथ दृश्य उस विशिष्ट प्रकार के प्रश्न का उपयोग करता है जिसे वह प्रदर्शित करने में सक्षम है, और मॉडल दृश्य से डिस्कनेक्ट हो गया है।

कारखाने को प्रतिबिंब के माध्यम से या मैन्युअल रूप से आवेदन शुरू होने पर आबाद किया जा सकता है।


यदि आप एक ऐसी प्रणाली में थे जहां दृश्य को कैशिंग करना महत्वपूर्ण था (जैसे कि एक खेल), तो कारखाने में प्रश्नावली का एक पूल शामिल हो सकता है।
एक्सट्रो

यह कैलेथ के उत्तर के समान प्रतीत होता है: आप अभी भी Questionएक कास्ट करने की आवश्यकता पर जा रहे हैं MultipleChoiceQuestionजब आपMultipleChoiceView
नाथन मेरिल

C # में कम से कम, मैं एक कास्ट के बिना ऐसा करने में कामयाब रहा। गेटव्यू पद्धति में, जब यह दृश्य उदाहरण बनाता है (Activator.CreateInstance (questionViewType, सवाल) कहकर), CreateInstance का दूसरा पैरामीटर निर्माता को भेजा गया पैरामीटर है। मेरा एक मल्टीच्यूअर व्यू कंस्ट्रक्टर केवल एक मल्टीफ़ोर्सक्वाइन स्वीकार करता है। शायद यह अभी CreateInstance फ़ंक्शन के अंदर कलाकारों को स्थानांतरित कर रहा है।
एक्सट्रॉस

0

मुझे यकीन नहीं है कि यह "प्रकार की जांच से बचने" के रूप में गिना जाता है, इस पर निर्भर करता है कि आप प्रतिबिंब के बारे में कैसा महसूस करते हैं ।

// Either statically associate or have a register(Class, Supplier) method
Dictionary<Class<? extends Question>, Supplier<? extends QuestionViewer>> 
viewerFactory = // MultipleChoice => MultipleChoiceViewer::new etc ...

// ... elsewhere

for (Question question: questions){
    display.add(viewerFactory[question.getClass()]());
}

यह मूल रूप से एक प्रकार की जाँच है, लेकिन एक ifप्रकार की जाँच से एक प्रकार की जाँच के लिए चलती है dictionary। जैसे कि पायथन स्विच स्टेटमेंट के बजाय शब्दकोशों का उपयोग कैसे करता है। उस ने कहा, अगर मैं बयानों की एक सूची से अधिक इस तरह से पसंद करता हूं ।
नाथन मेरिल 13

1
@NathanMerrill हाँ। जावा में दो वर्ग पदानुक्रमों को समानांतर रखने का एक अच्छा तरीका नहीं है। सी ++ में मैं template <typename Q> struct question_traits;उपयुक्त विशेषज्ञताओं के साथ सिफारिश
करूंगा

@ कैलेथ, क्या आप उस जानकारी को गतिशील रूप से एक्सेस कर सकते हैं? मुझे लगता है कि आपको एक उदाहरण दिया गया सही प्रकार का निर्माण करना होगा।
विंस्टन एवर्ट

इसके अलावा, फैक्ट्री को संभवत: प्रश्न उदाहरण की जरूरत है। इससे यह पैटर्न दुर्भाग्य से गड़बड़ हो जाता है, क्योंकि इसमें आमतौर पर बदसूरत कलाकारों की आवश्यकता होती है।
विंस्टन एवर्ट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.