डायनामिक_कास्ट के उपयोग से बचने के लिए उचित डिज़ाइन?


9

कुछ शोधों को करने के बाद मैं एक साधारण उदाहरण को खोजने में प्रतीत नहीं हो सकता है जो एक समस्या का समाधान करता है जिसका मैं अक्सर सामना करता हूं।

मान लीजिए कि मैं एक छोटा अनुप्रयोग बनाना चाहता हूं, जहां मैं Squares, Circles और अन्य आकार बना सकता हूं , उन्हें स्क्रीन पर प्रदर्शित कर सकता हूं, उनके चयन के बाद उनके गुणों को संशोधित कर सकता हूं और फिर उनकी सभी परिधि की गणना कर सकता हूं।

मैं मॉडल वर्ग को इस तरह से करूंगा:

class AbstractShape
{
public :
    typedef enum{
        SQUARE = 0,
        CIRCLE,
    } SHAPE_TYPE;

    AbstractShape(SHAPE_TYPE type):m_type(type){}
    virtual ~AbstractShape();

    virtual float computePerimeter() const = 0;

    SHAPE_TYPE getType() const{return m_type;}
protected :
    const SHAPE_TYPE  m_type;
};

class Square : public AbstractShape
{
public:
    Square():AbstractShape(SQUARE){}
    ~Square();

    void setWidth(float w){m_width = w;}
    float getWidth() const{return m_width;}

    float computePerimeter() const{
        return m_width*4;
    }

private :
    float m_width;
};

class Circle : public AbstractShape
{
public:
    Circle():AbstractShape(CIRCLE){}
    ~Circle();

    void setRadius(float w){m_radius = w;}
    float getRadius() const{return m_radius;}

    float computePerimeter() const{
        return 2*M_PI*m_radius;
    }

private :
    float m_radius;
};

(कल्पना करें कि मेरे पास आकृतियों की अधिक कक्षाएं हैं: त्रिकोण, हेक्सागोन्स, हर बार उनके प्रॉपर वैरिएबल और संबंधित गेटर्स और सेटर के साथ। मुझे जिन समस्याओं का सामना करना पड़ा उनमें 8 उपवर्ग थे लेकिन उदाहरण के लिए मैं 2 पर ही रुक गया।

अब मेरे पास एक ShapeManagerसरणी में सभी आकृतियों को संकलित करने और संग्रहीत करने के लिए है:

class ShapeManager
{
public:
    ShapeManager();
    ~ShapeManager();

    void addShape(AbstractShape* shape){
        m_shapes.push_back(shape);
    }

    float computeShapePerimeter(int shapeIndex){
        return m_shapes[shapeIndex]->computePerimeter();
    }


private :
    std::vector<AbstractShape*> m_shapes;
};

अंत में, मेरे पास प्रत्येक प्रकार के आकार के लिए प्रत्येक पैरामीटर को बदलने के लिए स्पिनबॉक्स के साथ एक दृश्य है। उदाहरण के लिए, जब मैं स्क्रीन पर एक वर्ग का चयन करता हूं, तो पैरामीटर विजेट केवल- Squareसंबंधित पैरामीटर (धन्यवाद AbstractShape::getType()) प्रदर्शित करता है और वर्ग की चौड़ाई को बदलने का प्रस्ताव करता है। ऐसा करने के लिए मुझे एक फ़ंक्शन की आवश्यकता है जिससे मैं चौड़ाई को संशोधित कर सकूं ShapeManagerऔर यह है कि मैं यह कैसे करूं:

void ShapeManager::changeSquareWidth(int shapeIndex, float width){
   Square* square = dynamic_cast<Square*>(m_shapes[shapeIndex]);
   assert(square);
   square->setWidth(width);
}

क्या मेरे द्वारा उपयोग dynamic_castकिए जाने वाले ShapeManagerप्रत्येक उपवर्ग चर के लिए एक गेट्टर / सेटर जोड़ी को लागू करने और उसका उपयोग करने के लिए मुझसे बचने के लिए एक बेहतर डिज़ाइन है? मैंने पहले ही टेम्प्लेट का उपयोग करने की कोशिश की लेकिन असफल रहा


समस्या मैं का सामना करना पड़ रहा हूँ आकृतियाँ साथ लेकिन साथ वास्तव में नहीं है अलग Jobरों : (उदा एक 3 डी प्रिंटर के लिए PrintPatternInZoneJob, TakePhotoOfZoneआदि) के साथ AbstractJobउनके आधार वर्ग के रूप में। आभासी विधि है execute()और नहीं getPerimeter()नौकरी के लिए आवश्यक विशिष्ट जानकारी को भरने के लिए मुझे केवल ठोस उपयोग की आवश्यकता है :

  • PrintPatternInZone प्रिंट करने के लिए अंकों की सूची, ज़ोन की स्थिति, तापमान जैसे कुछ मुद्रण पैरामीटर की आवश्यकता होती है

  • TakePhotoOfZone जरूरत है कि किस क्षेत्र को फोटो में लिया जाए, वह मार्ग जहां फोटो को सहेजा जाएगा, आयाम, आदि ...

जब मैं फोन करूंगा execute(), जॉब्स को उन विशिष्ट सूचनाओं का उपयोग करना होगा जो उन्हें उस कार्रवाई का एहसास करना है जो वे करने वाले हैं।

केवल एक बार मुझे नौकरी के ठोस प्रकार का उपयोग करने की आवश्यकता होती है, जब मैं थिसिस informations भरता हूं या प्रदर्शित करता हूं (यदि TakePhotoOfZone Jobयह चुना गया है, तो क्षेत्र, पथ और आयाम मापदंडों को प्रदर्शित करने और संशोधित करने वाला एक विजेट दिखाया जाएगा)।

Jobरों तब की एक सूची में डाल दिया जाता है Jobरों जो पहली नौकरी ले, वह निष्पादित (फोन करके AbstractJob::execute()), अगली सूची के अंत तक पर और पर, को जाता है। (यही कारण है कि मैं विरासत का उपयोग करता हूं)।

विभिन्न प्रकार के मापदंडों को संग्रहीत करने के लिए मैं एक का उपयोग करता हूं JsonObject:

  • फायदे: किसी भी नौकरी के लिए एक ही संरचना, पैरामीटर सेट करते या पढ़ते समय कोई डायनेमिक_कास्ट नहीं

  • समस्या: संकेत ( Patternया करने के लिए Zone) स्टोर नहीं कर सकते

क्या आप डेटा स्टोर करने का एक बेहतर तरीका है?

फिर जब आप उस प्रकार के विशिष्ट मापदंडों को संशोधित करना होगा, तो आपJob इसका उपयोग करने के लिए ठोस प्रकार को कैसे स्टोर करेंगे ? JobManagerकी एक सूची है AbstractJob*


5
आपके शेपमैनगर की तरह लगता है कि यह एक ईश्वर वर्ग बन जाएगा, क्योंकि इसमें मूल रूप से सभी प्रकार के आकार के लिए सभी सेटर विधियां होंगी।
इमर्सन कार्डसो

क्या आपने "संपत्ति बैग" डिजाइन पर विचार किया है? जैसे कि एनम या स्ट्रिंग changeValue(int shapeIndex, PropertyKey propkey, double numericalValue)कहां PropertyKeyहो सकती है, और "चौड़ाई" (जो दर्शाता है कि सेटर को कॉल चौड़ाई का मूल्य अपडेट करेगा) अनुमत मानों में से एक है।
रवांग

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

मैंने प्रॉपर्टी बैग डिज़ाइन पर विचार किया (भले ही मुझे इसका नाम नहीं पता था) लेकिन एक JSON ऑब्जेक्ट कंटेनर के साथ। यह सुनिश्चित हो सकता है कि काम हो सकता है लेकिन मुझे लगा कि यह एक सुरुचिपूर्ण डिजाइन नहीं है और यह एक बेहतर विकल्प हो सकता है। इसे OO का प्रतिमान क्यों माना जाता है?
ग्यारहवीं जून

उदाहरण के लिए, अगर मैं बाद में इसका उपयोग करने के लिए एक पॉइंटर स्टोर करना चाहता हूं, तो मैं कैसे करूं?
इलेवनज्यून

जवाबों:


10

मैं इमर्सन कार्डसो के "अन्य सुझाव" पर विस्तार करना चाहूंगा क्योंकि मेरा मानना ​​है कि यह सामान्य मामले में सही दृष्टिकोण है - हालांकि आप निश्चित रूप से किसी विशेष समस्या के लिए अन्य समाधानों को बेहतर तरीके से ढूंढ सकते हैं।

समस्या

आपके उदाहरण में, AbstractShapeक्लास में एक getType()विधि है जो मूल रूप से कंक्रीट प्रकार की पहचान करती है। यह आमतौर पर एक संकेत है कि आप एक अच्छा अमूर्त नहीं है। सार के पूरे बिंदु, आखिरकार, ठोस प्रकार के विवरण के बारे में परवाह नहीं है।

इसके अलावा, यदि आप इससे परिचित नहीं हैं, तो आपको ओपन / बंद सिद्धांत पर पढ़ना चाहिए। इसे अक्सर आकृतियों के उदाहरण के साथ समझाया जाता है, ताकि आप घर पर सही महसूस करें।

उपयोगी सार

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

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

आवेदन के अन्य भाग केवल एक विशेष प्रकार के आकार के साथ काम करेंगे, जैसे Rectangle। उन क्षेत्रों को एक AbstractShapeवर्ग से लाभ नहीं होगा , इसलिए आपको इसका उपयोग नहीं करना चाहिए। इन भागों में केवल सही आकार पारित करने के लिए, आपको अलग से ठोस आकृतियों को संग्रहीत करने की आवश्यकता है। (आप उन्हें AbstractShapeअतिरिक्त रूप से संग्रहीत कर सकते हैं , या उन्हें मक्खी पर जोड़ सकते हैं)।

न्यूनतम उपयोग को कम करना

इसके आसपास कोई रास्ता नहीं है: आपको कुछ स्थानों पर कंक्रीट के प्रकारों की आवश्यकता है - निर्माण के दौरान बहुत कम से कम। हालांकि, कभी-कभी कंक्रीट के उपयोग को कुछ अच्छी तरह से परिभाषित क्षेत्रों तक सीमित रखना सबसे अच्छा है। इन अलग-अलग क्षेत्रों में विभिन्न प्रकारों से निपटने का एकमात्र उद्देश्य है - जबकि सभी अनुप्रयोग तर्क उनमें से बाहर रखे गए हैं।

आप इसे कैसे प्राप्त करेंगे? आमतौर पर, अधिक अमूर्तताएं शुरू करने से - जो मौजूदा अमूर्तताओं को प्रतिबिंबित कर सकते हैं या नहीं कर सकते हैं। उदाहरण के लिए, आपके GUI को वास्तव में यह जानने की आवश्यकता नहीं है कि वह किस प्रकार की आकृति के साथ काम कर रहा है। बस यह जानना होगा कि स्क्रीन पर एक क्षेत्र है जहां उपयोगकर्ता किसी आकृति को संपादित कर सकता है।

तो आप एक सार को परिभाषित ShapeEditViewकरते हैं जिसके लिए आपके पास RectangleEditViewऔर CircleEditViewकार्यान्वयन हैं जो चौड़ाई / ऊंचाई या त्रिज्या के लिए वास्तविक टेक्स्ट बॉक्स रखते हैं।

पहले चरण में, आप RectangleEditViewजब भी कोई निर्माण कर सकते हैं, Rectangleतब उसे एक में डाल सकते हैं std::map<AbstractShape*, AbstractShapeView*>। यदि आप अपनी आवश्यकता के अनुसार विचार पैदा करेंगे, तो आप इसके बजाय निम्नलिखित कर सकते हैं:

std::map<AbstractShape*, std::function<AbstractShapeView*()>> viewFactories;
// ...
auto rect = new Rectangle();
// ...
auto viewFactory = [rect]() { return new RectangleEditView(rect); }
viewFactories[rect] = viewFactory;

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

सही विकल्प चुनना

बहुत ही सरल अनुप्रयोगों में, आप पा सकते हैं कि एक गंदा (कास्टिंग) समाधान आपको अपने हिरन के लिए सबसे अधिक धमाके देता है।

स्पष्ट रूप से प्रत्येक ठोस प्रकार के लिए अलग-अलग सूचियों को बनाए रखना संभवतया यह है कि यदि आपका आवेदन मुख्य रूप से ठोस आकृतियों से संबंधित है, लेकिन कुछ हिस्से ऐसे हैं जो सार्वभौमिक हैं। यहाँ, यह केवल सार के लिए समझ में आता है क्योंकि सामान्य कार्यक्षमता के लिए इसकी आवश्यकता होती है।

सभी तरह से जाना आम तौर पर भुगतान करता है यदि आपके पास बहुत सारे तर्क हैं जो आकृतियों पर काम करते हैं, और सही तरह की आकृति वास्तव में आपके आवेदन का एक विवरण है।


मुझे वास्तव में आपका उत्तर पसंद है, आपने पूरी तरह से समस्या का वर्णन किया है। मैं जो समस्या का सामना कर रहा हूँ, वह वास्तव में आकृतियों के साथ नहीं है, बल्कि 3 डी प्रिंटर के लिए अलग-अलग जॉब्स के साथ है (उदाहरण: PrintPatternInZoneJob, TakePhotoOfZone, आदि) AbstractJob के साथ उनके आधार वर्ग के रूप में। वर्चुअल विधि निष्पादित है (और नहीं getPerimeter ()। एक विशिष्ट विजेट के साथ नौकरी की ज़रूरतों (बिंदुओं, स्थिति, तापमान, आदि की सूची) को भरने के लिए मुझे केवल एक बार ठोस उपयोग करने की आवश्यकता होती है। प्रत्येक कार्य के लिए एक दृश्य संलग्न करना इस विशेष मामले में करने वाली बात नहीं लगती है, लेकिन मैं यह नहीं देखता कि मैं अपनी दृष्टि को अपने pb में कैसे ढालूँ।
इलेवनज्यून

आप अलग-अलग सूचियों रखने के लिए नहीं करना चाहते हैं, तो आप एक viewFactory के बजाय एक viewSelector उपयोग कर सकते हैं: [rect, rectView]() { rectView.bind(rect); return rectView; }। वैसे, यह निश्चित रूप से प्रस्तुति मॉड्यूल में किया जाना चाहिए, उदाहरण के लिए एक RectangleCreatedEventHandler में।
डबल यू

3
यह कहा जा रहा है, यह अधिक इंजीनियर करने की कोशिश नहीं की। अमूर्तता का लाभ अभी भी अतिरिक्त आलूबुखारा की लागत से आगे बढ़ना चाहिए। कभी-कभी एक अच्छी तरह से रखा कास्ट, या अलग तर्क बेहतर हो सकता है।
डबल यू

2

एक दृष्टिकोण सामान को अधिक सामान्य बनाना होगा क्रम में विशिष्ट प्रकार के लिए कास्टिंग से बचने

आप आधार वर्ग में " आयाम " फ्लोट गुणों के एक मूल गेट्टर / सेटर को लागू कर सकते हैं , जो संपत्ति के नाम के लिए एक विशिष्ट कुंजी के आधार पर, एक मानचित्र में एक मूल्य निर्धारित करता है। नीचे उदाहरण:

class AbstractShape
{
public :
    typedef enum{
        SQUARE = 0,
        CIRCLE,
    } SHAPE_TYPE;

    AbstractShape(SHAPE_TYPE type):m_type(type){}
    virtual ~AbstractShape();

    virtual float computePerimeter() const = 0;

    void setDimension(const std::string& name, float v){ m_dimensions[name] = v; }
    float getDimension() const{ return m_dimensions[name]; }

    SHAPE_TYPE getType() const{return m_type;}

protected :
    const SHAPE_TYPE  m_type;
    std::map<std::string, float> m_dimensions;
};

फिर, आपके प्रबंधक वर्ग में आपको केवल एक फ़ंक्शन लागू करने की आवश्यकता है, जैसे नीचे:

void ShapeManager::changeShapeDimension(const int shapeIndex, const std::string& dimension, float value){
   m_shapes[shapeIndex]->setDimension(name, value);
}

दृश्य के भीतर उपयोग का उदाहरण:

ShapeManager shapeManager;

shapeManager.addShape(new Circle());
shapeManager.changeShapeDimension(0, "RADIUS", 5.678f);
float circlePerimeter = shapeManager.computeShapePerimeter(0);

shapeManager.addShape(new Square());
shapeManager.changeShapeDimension(1, "WIDTH", 2.345f);
float squarePerimeter = shapeManager.computeShapePerimeter(1);

एक और सुझाव:

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

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

मुझे पहला सुझाव पसंद है और पहले से ही इसके बारे में सोचा है, लेकिन अगर आप विभिन्न चर (फ्लोट, पॉइंटर्स, सरणियों) को स्टोर करना चाहते हैं तो यह काफी सीमित है। दूसरे सुझाव के लिए, यदि चौकोर पहले से ही तात्कालिक है (मैंने इस पर विचार किया) तो मुझे कैसे पता चलेगा कि यह एक वर्ग है? आकृतियों को संग्रहीत करने वाली सूची एक AbstractShape * लौटाती है ।
इलेवनज्यून

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

@ElevenJune - दृश्य ऑब्जेक्ट होने का पूरा बिंदु आपके GUI को यह जानने की आवश्यकता नहीं है कि यह वर्ग के वर्ग के साथ काम कर रहा है। व्यू ऑब्जेक्ट प्रदान करता है कि ऑब्जेक्ट को "देखने" के लिए क्या आवश्यक है (जो भी आप इसे परिभाषित करते हैं) और आंतरिक रूप से यह जानता है कि यह एक वर्ग वर्ग के उदाहरण का उपयोग कर रहा है। GUI केवल SquareView उदाहरण के साथ इंटरैक्ट करता है। इस प्रकार, आप 'स्क्वायर' वर्ग पर क्लिक नहीं कर सकते। आप केवल स्क्वायर व्यू क्लास पर क्लिक कर सकते हैं। SquareView पर पैरामीटर बदलने से अंतर्निहित स्क्वायर क्लास अपडेट हो जाएगा ....
Dunk

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

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