2010-02-28 12 views
6

मान लें कि हमारे पास पहले से ही कक्षाओं का पदानुक्रम है, उदाहरण के लिएमूल कक्षाओं को संशोधित किए बिना वर्चुअल फ़ंक्शंस जोड़ना

class Shape { virtual void get_area() = 0; }; 
class Square : Shape { ... }; 
class Circle : Shape { ... }; 
etc. 

अब चलो कहना है कि मैं करने के लिए (प्रभावी रूप से) एक virtual draw() = 0 विधि Shape करने के लिए प्रत्येक उप-वर्ग में उचित परिभाषा के साथ जोड़ने चाहते हैं। हालांकि, मान लें कि मैं उन वर्गों को संशोधित किए बिना ऐसा करना चाहता हूं (क्योंकि वे उस पुस्तकालय का हिस्सा हैं जिन्हें मैं बदलना नहीं चाहता)।

इस बारे में जाने का सबसे अच्छा तरीका क्या होगा?

चाहे मैं वास्तव में virtual विधि "जोड़" या नहीं, महत्वपूर्ण नहीं है, मैं सिर्फ पॉलीमोर्फिक व्यवहार को पॉइंटर्स की एक सरणी देता हूं।

मेरी पहली सोचा यह करने के लिए होगा:

class IDrawable { virtual void draw() = 0; }; 
class DrawableSquare : Square, IDrawable { void draw() { ... } }; 
class DrawableCircle : Circle, IDrawable { void draw() { ... } }; 

और फिर बस क्रमश: DrawableSquare और DrawableCircle रों साथ Square रों के सभी कृतियों और Circle रों बदलें।

क्या यह पूरा करने का सबसे अच्छा तरीका है, या कुछ बेहतर है (अधिमानतः कुछ ऐसा जो Square एस और Circle एस के बरकरार छोड़ देता है)।

अग्रिम धन्यवाद।

उत्तर

5

(मैं मेरे साथ और नीचे एक समाधान प्रस्तावित कर ... भालू ...)

एक तरह से करने के लिए (लगभग) आपकी समस्या का समाधान एक आगंतुक डिजाइन पैटर्न का उपयोग करने के लिए है। कुछ इस तरह:

class DrawVisitor 
{ 
public: 
    void draw(const Shape &shape); // dispatches to correct private method 
private: 
    void visitSquare(const Square &square); 
    void visitCircle(const Circle &circle); 
}; 
तो बजाय इस बात का

:

Shape &shape = getShape(); // returns some Shape subclass 
shape.draw(); // virtual method 

आप करना होगा:

DrawVisitor dv; 
Shape &shape = getShape(); 
dv.draw(shape); 

आम तौर पर एक आगंतुक पैटर्न में आप draw विधि इस तरह लागू करना होगा:

DrawVisitor::draw(const Shape &shape) 
{ 
    shape.accept(*this); 
} 

लेकिन यह केवल तभी काम करता है जब Shape पदानुक्रम का दौरा करने के लिए डिज़ाइन किया गया था: प्रत्येक उप-वर्ग विज़िटर पर उपयुक्त visitXxxx विधि को कॉल करके वर्चुअल विधि accept लागू करता है। सबसे अधिक संभावना है कि यह इसके लिए डिजाइन नहीं किया गया था।

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

DrawVisitor::draw(const Shape &shape) 
{ 
    if (const Square *pSquare = dynamic_cast<const Square *>(&shape)) 
    { 
    visitSquare(*pSquare); 
    } 
    else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape)) 
    { 
    visitCircle(*pCircle); 
    } 
    // etc. 
} 

यह काम करेगा, लेकिन गतिशील_कास्ट का उपयोग करने के लिए एक प्रदर्शन हिट है। आपको लगता है कि हिट खर्च कर सकते हैं, तो यह एक स्पष्ट दृष्टिकोण को समझने के लिए, डिबग, बनाए रखने के लिए आसान है कि, आदि

है मान लीजिए सभी आकार प्रकार के एक गणन नहीं थी:

enum ShapeId { SQUARE, CIRCLE, ... }; 

और वहाँ एक आभासी था विधि ShapeId Shape::getId() const = 0; कि प्रत्येक सबक्लास अपने ShapeId को वापस करने के लिए ओवरराइड करेगा। फिर आप dynamic_cast एस के if-elsif-elsif के बजाय बड़े पैमाने पर switch कथन का उपयोग करके अपना प्रेषण कर सकते हैं। या शायद switch के बजाय हैशटेबल का उपयोग करें। सबसे अच्छा मामला परिदृश्य यह मैपिंग फ़ंक्शन एक ही स्थान पर रखना है, ताकि आप प्रत्येक बार मैपिंग तर्क दोहराने के बिना एकाधिक विज़िटर को परिभाषित कर सकें।

तो आपके पास शायद getid() विधि नहीं है। बहुत बुरा। प्रत्येक प्रकार की ऑब्जेक्ट के लिए अद्वितीय आईडी प्राप्त करने का दूसरा तरीका क्या है? RTTI। यह आवश्यक रूप से सुरुचिपूर्ण या मूर्ख नहीं है, लेकिन आप type_info पॉइंटर्स का हैशटेबल बना सकते हैं। आप कुछ प्रारंभिक कोड में इस हैशटेबल को बना सकते हैं या इसे गतिशील रूप से (या दोनों) बना सकते हैं।

DrawVisitor::init() // static method or ctor 
{ 
    typeMap_[&typeid(Square)] = &visitSquare; 
    typeMap_[&typeid(Circle)] = &visitCircle; 
    // etc. 
} 

DrawVisitor::draw(const Shape &shape) 
{ 
    type_info *ti = typeid(shape); 
    typedef void (DrawVisitor::*VisitFun)(const Shape &shape); 
    VisitFun visit = 0; // or default draw method? 
    TypeMap::iterator iter = typeMap_.find(ti); 
    if (iter != typeMap_.end()) 
    { 
    visit = iter->second; 
    } 
    else if (const Square *pSquare = dynamic_cast<const Square *>(&shape)) 
    { 
    visit = typeMap_[ti] = &visitSquare; 
    } 
    else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape)) 
    { 
    visit = typeMap_[ti] = &visitCircle; 
    } 
    // etc. 

    if (visit) 
    { 
    // will have to do static_cast<> inside the function 
    ((*this).*(visit))(shape); 
    } 
} 

वहां कुछ बग/वाक्यविन्यास त्रुटियां हो सकती हैं, मैंने इस उदाहरण को संकलित करने की कोशिश नहीं की है। मैंने पहले ऐसा कुछ किया है - तकनीक काम करती है। मुझे यकीन नहीं है कि क्या आप साझा पुस्तकालयों के साथ समस्याओं में भाग ले सकते हैं।

मैं जोड़ देंगे एक आखिरी बात: कैसे आप प्रेषण करने का फैसला की परवाह किए बिना, यह शायद समझ में आता है एक आगंतुक आधार वर्ग बनाने के लिए:

class ShapeVisitor 
{ 
public: 
    void visit(const Shape &shape); // not virtual 
private: 
    virtual void visitSquare(const Square &square) = 0; 
    virtual void visitCircle(const Circle &circle) = 0; 
}; 
+0

क्या आपका मतलब है यात्रा के बजाय 'सर्किल (कॉन्स सर्किल एंड सर्कल)' यात्रा करें? –

+0

@ फिलिप: ओह ... तय। – Dan

+0

दिलचस्प समाधान, मुझे 'विज़िटर' पैटर्न के केवल एक हिस्से का उपयोग पसंद है >> पैटर्न को स्थिति के अनुकूल बनाया जाना है, न कि दूसरी तरफ :) –

3

जो आप वर्णन कर रहे हैं वह कुछ हद तक decorator pattern जैसा है। जो मौजूदा वर्गों के रनटाइम व्यवहार को बदलने के लिए बहुत उपयुक्त है।

लेकिन मैं सच में कैसे अपने व्यावहारिक उदाहरण लागू करने के लिए नहीं दिख रहा है, यदि आकार कोई रास्ता तैयार किया जा करने के लिए है, तो कोई रास्ता क्रम या तो ...

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

+0

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

+0

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

+0

ऐसा लगता है कि बुलेट एसडीके में 'btIDebugDraw' इंटरफ़ेस है: http://bulletphysics.com/Bullet/BulletFull/classbtIDebugDraw.html। आप उस से एक वर्ग प्राप्त करते हैं जो 'drawBox()' और 'drawSphere()' जैसे कार्यों को लागू करता है, फिर इसे 'btCollisionWorld' या 'btDynamicsWorld' को असाइन करें, और फिर इसे उस दुनिया को आकर्षित करें। क्या आपको वह चाहिए जो आपको चाहिए? –

0

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

#include <iostream> 

using namespace std; 

// This bit's a bit like your library. 
struct Square{}; 
struct Circle{}; 
struct AShape{}; 

// and this is your extra stuff. 
template < class T > 
class Drawable { public: void draw() const { cout << "General Shape" << endl; } }; 

template <> void Drawable<Square>::draw() const { cout << "Square!" << endl; }; 
template <> void Drawable<Circle>::draw() const { cout << "Circle!" << endl; }; 

template < class T > 
void drawIt(const T& obj) 
{ 
    obj.draw(); 
} 

int main(int argc, char* argv[]) 
{ 
    Drawable<Square> a; 
    Drawable<Circle> b; 
    Drawable<AShape> c; 

    a.draw(); // prints "Square!" 
    b.draw(); // prints "Circle!" 
    c.draw(); // prints "General Shape" as there's no specific specialisation for an Drawable<AShape> 

    drawIt(a); // prints "Square!" 
    drawIt(b); // prints "Circle!" 
    drawIt(c); // prints "General Shape" as there's no specific specialisation for an Drawable<AShape> 
} 

drawIt() विधि शायद यहां महत्वपूर्ण बात है क्योंकि यह draw() विधि होने की आवश्यकता को पूरा करने के लिए किसी वर्ग की सामान्य बैठक का प्रतिनिधित्व करती है। यहां कोड ब्लोट के लिए देखें, हालांकि कंपाइलर प्रत्येक प्रकार के पारित होने के लिए एक अलग विधि को तुरंत चालू करेगा।

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

संबंधित मुद्दे