2015-03-13 8 views
14

const रहे हैं मैं एक वर्ग डिजाइन निम्न के समान है। MakeValid गैर-आधार होना चाहिए, क्योंकि यह परिवर्तन करता है। वे एक ही कार्यान्वयन साझा करते हैं, CheckValidity, लेकिन CheckValidity परिवर्तन कर सकते हैं या नहीं भी कर सकते हैं, इसे const चिह्नित नहीं किया जा सकता है।सदस्य कार्यों है कि कभी कभी

इसे संभालने का सबसे अच्छा तरीका क्या है? सरल दृष्टिकोण सिर्फ const_cast उपयोग करने के लिए है, लेकिन दूर कास्टिंग स्थिरांक थोड़ा गंदा लगता है:

bool MyClass::IsValid() const { 
    // Check validity, but don't fix any problems found. 
    return const_cast<MyClass*>(this)->CheckValidity(false); 
} 

इस const_cast के एक वैध उपयोग है? क्या कोई बेहतर दृष्टिकोण है?

+14

स्प्लिट चेक और "फिक्सिंग" दो अलग-अलग कार्यों में? चेक 'const' है और ठीक नहीं है? – crashmstr

+6

मैं @crashmstr से सहमत हूं, जिसमें एक कार्य है जो दो अलग-अलग चीजें करता है, एक खराब डिजाइन गंध है। –

+5

असल में, पहले से ही 'चेक वैलिडिटी' नाम से पता चलता है कि यह फ़ंक्शन केवल एक चेक करता है और यह हो सकता है। यदि आप चाहते हैं करने के लिए 'MakeValid' यह कुछ अलग है (और यह स्थिरांक नहीं होना चाहिए ...) – user463035818

उत्तर

16

मैं अपने कार्यान्वयन संभालने कर रहा हूँ इस के समान दिखता है:

bool CheckValidity(bool fix) 
{ 
    // Actually check validity. 
    bool isValid = ...; 

    if (!isValid && fix) 
    { 
     // Attempt to fix validity (and update isValid). 
     isValid = ...; 
    } 

    return isValid; 
} 

आप वास्तव में दो अलग-अलग कार्यों में shoved है। इस प्रकार के उलझन के प्रमुख संकेतकों में से एक कार्य के लिए बुलियन तर्क है ... जो गंध करता है क्योंकि कॉलर तुरंत यह नहीं समझ सकता कि कोड/दस्तावेज़ों के संदर्भ में सही या गलत रखना है या नहीं।

bool CheckValidity() const 
{ 
    // Actually check validity. 
    bool isValid = ...; 
    return isValid; 
} 

void FixValidity() 
{ 
    // Attempt to fix validity. 
    // ... 
} 

और फिर अपने सार्वजनिक तरीकों कॉल अधिक उचित रूप से बना सकते हैं:

विधि विभाजित करें।

bool IsValid() const 
{ 
    // No problem: const method calling const method 
    return CheckValidity(); 
} 

void MakeValid() 
{ 
    if (!CheckValidity()) // No problem: non-const calling const 
    { 
     FixValidity(); // No problem: non-const calling non-const 
    } 
} 
+1

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

+5

@ जोशकेली यह हो सकता है कि आप ट्रैवर्सल को हेरफेर से जांचने से अलग कर सकें। या आप चेक बनाम फिक्स को अलग करना मुश्किल हो सकता है क्योंकि आपके मौजूदा कोड/संरचनाओं का चयन किया जाता है। चेक और फ़िक्स में विभाजित करने का एक "आसान" तरीका बस आपकी वर्तमान चेकवैलिटी (बूल) विधि को डुप्लिकेट करना है। सबसे पहले, मान लें कि उस विधि में बूल हर जगह झूठा है, मृत कोड को हटा दें, फिर बूल पैरामीटर को हटा दें। दूसरे के साथ, FixValidity का नाम बदलें, मान लें कि बूल सत्य है, मृत कोड को हटा दें, फिर बूल पैरामीटर हटा दें। फिर देखें कि दो और रिफैक्टर के बीच अभी भी क्या आम है। –

+0

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

6

यहां एक ऐसा दृष्टिकोण है जो कुछ मामलों में उपयोगी हो सकता है। यह आपकी विशेष स्थिति के लिए अधिक हो सकता है।

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

class MyClass { 
public: 
    bool IsValid() const 
    { 
     bool flag = false; 
     CheckValidity(FlagProblems{flag}); 
     return flag; 
    } 

    void MakeValid() 
    { 
     CheckValidity(FixProblems{*this}); 
    } 

private: 
    struct FlagProblems { 
     bool& flag; 

     void handleType1(arg1,arg2)  const { flag = true; } 
     void handleType2(arg1,arg2,arg3) const { flag = true; } 
     . 
     . 
     . 
    }; 

    struct FixProblems { 
     MyClass& object; 
     void handleType1(arg1,arg2)  const { ... } 
     void handleType2(arg1,arg2,arg3) const { ... } 
     . 
     . 
     . 
    }; 

    template <typename Handler> 
    bool CheckValidity(const Handler &handler) const 
    { 
     // for each possible problem: 
     // if it is a type-1 problem: 
     //  handler.handleType1(arg1,arg2); 
     // if it is a type-2 problem: 
     //  handler.handleType2(arg1,arg2,arg3); 
     // . 
     // . 
     // . 
    } 
}; 

टेम्पलेट का उपयोग करना:

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

यदि ऑब्जेक्ट्स अमान्य हो सकते हैं, तो सरल तरीका है, तो चेकवैलिटी प्रासंगिक जानकारी वाले एक स्ट्रक्चर को वापस लेना अधिक सरल हो सकता है।

+0

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

+1

@ मैथ्यूमोस: मेरा वास्तव में मतलब था कि हम डेटा संरचना में प्रत्येक संभावित वस्तु के बजाय प्रत्येक संभावित समस्या के बावजूद जा रहे हैं। मैं इसे दोबारा बताऊंगा। –

+0

आह, समझा। –

2

आप उन हिस्सों को अलग करने के लिए टेम्पलेट विशेषज्ञता का उपयोग कर सकते हैं जिनके पास केवल गैर-कॉन्स्ट ऑब्जेक्ट पर उद्देश्य है।

खिलौना वर्ग के लिए एक कार्यान्वयन निम्नलिखित है। इसमें 10 इंच के साथ एक एकल सी-सरणी सदस्य बनाम है, और, हमारे उद्देश्यों के लिए, यह केवल तभी मान्य होता है जब उनमें से प्रत्येक शून्य के बराबर होता है।

class ten_zeroes { 
    int v[10]; 
    void fix(int pos) {v[pos] = 0;} 

    public: 
    ten_zeroes() { // construct as invalid object 
    for (int i=0;i<10;i++) { 
     v[i] = i; 
    } 
    } 
}; 

देखें कि मैं पहले से ही एक समारोह सदस्य है कि गलत स्थिति ठीक करता है, और एक अच्छा निर्माता है कि यह गलत वस्तु के रूप में आरंभ किए गए (ऐसा नहीं है: डी)

हम करने जा रहे हैं के बाद से टेम्पलेट का उपयोग करें, हमें कक्षा के बाहर चेक/फिक्स चक्र के कार्यान्वयन को स्थानांतरित करने की आवश्यकता है। प्रासंगिक कार्यों के लिए v और fix() विधि तक पहुंचने में सक्षम होने के लिए, हम उन्हें मित्र बना देंगे। यहाँ

// Check and maybe fix object 
template<typename T> 
bool check(T& obj){ 
    bool result = true; 
    for(int i=0;i<10;i++) { 
    if (obj.v[i]) { 
     result = false; 
     fix(obj, i); 
    } 
    } 
    return result; 
} 

अब मुश्किल हिस्सा है: हमारे कोड अब लगता है कि:

class ten_zeroes { 
    int v[10]; 
    void fix(int pos) {v[pos] = 0;} 

    public: 
    ten_zeroes() { // construct as invalid object 
    for (int i=0;i<10;i++) { 
     v[i] = i; 
    } 
    } 

    template<typename T> 
    friend void fix(T& obj, int pos); 

    template<typename T> 
    friend bool check(T& obj); 
}; 

check() के कार्यान्वयन सरल है। हम दृढ़ता के आधार पर व्यवहार बदलने के लिए हमारे fix() फ़ंक्शन चाहते हैं। इसके लिए हमें टेम्पलेट का विशेषज्ञ होना होगा। एक गैर-कॉन्स ऑब्जेक्ट के लिए, यह स्थिति को ठीक करेगा। एक स्थिरांक एक के लिए, यह कुछ भी नहीं होगा:

// For a regular object, fix the position 
template<typename T> 
void fix(T& obj, int pos) { obj.fix(pos);} 

// For a const object, do nothing 
template<typename T> 
void fix(const T& obj, int pos) {} 

अंत में, हम हमारे is_valid() और make_valid() तरीकों लिखते हैं, और यहाँ हम पूर्ण कार्यान्वयन है:

#include <iostream> 

class ten_zeroes { 
    int v[10]; 
    void fix(int pos) {v[pos] = 0;} 

    public: 
    ten_zeroes() { // construct as invalid object 
    for (int i=0;i<10;i++) { 
     v[i] = i; 
    } 
    } 

    bool is_valid() const {return check(*this);} // since this is const, it will run check with a const ten_zeroes object 
    void make_valid() { check(*this);} // since this is non-const , it run check with a non-const ten_zeroes object 

    template<typename T> 
    friend void fix(T& obj, int pos); 

    template<typename T> 
    friend bool check(T& obj); 
}; 

// For a regular object, fix the position 
template<typename T> 
void fix(T& obj, int pos) { obj.fix(pos);} 

// For a const object, do nothing 
template<typename T> 
void fix(const T& obj, int pos) {} 

// Check and maybe fix object 
template<typename T> 
bool check(T& obj){ 
    bool result = true; 
    for(int i=0;i<10;i++) { 
    if (obj.v[i]) { 
     result = false; 
     fix(obj, i); 
    } 
    } 
    return result; 
} 

int main(){ 
    ten_zeroes a; 
    std::cout << a.is_valid() << a.is_valid(); // twice to make sure the first one didn't make any changes 
    a.make_valid(); // fix the object 
    std::cout << a.is_valid() << std::endl; // check again 
} 

मुझे आशा है कि आप कोई आपत्ति नहीं है main() वहां कार्य करें। यह हमारे छोटे खिलौने का परीक्षण करेगा, और उम्मीद के अनुसार 001 आउटपुट करेगा। अब इस कोड पर किसी भी रखरखाव को कोड डुप्लिकेशंस से निपटना नहीं होगा, जो आप शायद टालने का इरादा रखते थे। मुझे आशा है कि यह मददगार था।

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

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