2014-05-05 7 views
10

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

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

SomeAlgorithm algorithmWithEuclidean(new EuclideanDistanceCalculator()); 
SomeAlgorithm algorithmWithManhattan(new ManhattanDistanceCalculator()); 

लेकिन स्मार्ट संकेत के साथ मैनुअल new और delete से बचने के लिए: हमारा लक्ष्य कहने के लिए कुछ तरह सक्षम हो किया जा सके। यह कच्चे संकेत के साथ एक संभव कार्यान्वयन है:

class DistanceCalculator { 
public: 
    virtual double distance(Point p1, Point p2) = 0; 
}; 

class EuclideanDistanceCalculator { 
public: 
    virtual double distance(Point p1, Point p2) { 
     return sqrt(...); 
    } 
}; 

class ManhattanDistanceCalculator { 
public: 
    virtual double distance(Point p1, Point p2) { 
     return ...; 
    } 
}; 

class SomeAlgorithm { 
    DistanceCalculator* distanceCalculator; 

public: 
    SomeAlgorithm(DistanceCalculator* distanceCalculator_) 
     : distanceCalculator(distanceCalculator_) {} 

    double calculateComplicated() { 
     ... 
     double dist = distanceCalculator->distance(p1, p2); 
     ... 
    } 

    ~SomeAlgorithm(){ 
     delete distanceCalculator; 
    } 
}; 

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

एक समाधान जो दिमाग में आता है उसे संदर्भ-से-कॉन्स द्वारा पास करना है और std::unique_ptr<DistanceCalculator> सदस्य चर में इसे समाहित करना है। फिर कॉल होगा:

SomeAlgorithm algorithmWithEuclidean(EuclideanDistance()); 

लेकिन इस ढेर-आवंटित अस्थायी वस्तु (rvalue-संदर्भ?) इस लाइन के बाद विलुप्त हो जाएगा। तो हमें पास-दर-मूल्य की तरह इसे बनाने के लिए कुछ प्रतिलिपि की आवश्यकता होगी। लेकिन चूंकि हम रनटाइम प्रकार को नहीं जानते हैं, इसलिए हम आसानी से हमारी प्रतिलिपि नहीं बना सकते हैं।

हम कन्स्ट्रक्टर पैरामीटर के रूप में एक स्मार्ट सूचक का भी उपयोग कर सकते हैं। चूंकि स्वामित्व के साथ कोई समस्या नहीं है (DistanceCalculator का स्वामित्व SomeAlgorithm होगा) हमें std::unique_ptr का उपयोग करना चाहिए। क्या मुझे वास्तव में unique_ptr के साथ ऐसे सभी कन्स्ट्रक्टर पैरामीटर को प्रतिस्थापित करना चाहिए? ऐसा लगता है कि पठनीयता कम हो जाती है। इसके अलावा SomeAlgorithm के उपयोगकर्ता एक अजीब तरीके से निर्माण करना होगा:

SomeAlgorithm algorithmWithEuclidean(std::unique_ptr<DistanceCalculator>(new EuclideanDistance())); 

या मैं किसी तरह से नई चाल अर्थ विज्ञान (& &, std :: चाल) इस्तेमाल करना चाहिए?

यह एक बहुत ही मानक समस्या प्रतीत होता है, इसे लागू करने के लिए कुछ संक्षिप्त तरीका होना चाहिए।

SomeAlgorithm(std::function<double(Point,Point)> distanceCalculator_) 

प्रकार मिट मंगलाचरण वस्तु:

+0

आपको यह प्रश्न दिलचस्प भी मिल सकता है: http://stackoverflow.com/questions/4469304/dependency-injection-framework-for-c –

उत्तर

9

यदि मैं यह करना चाहता था, पहली बात मैं करता हूँ कि आपके द्वारा इंटरफेस को मारने, और बजाय इसका उपयोग है।

मैं का उपयोग कर एक ड्रॉप में प्रतिस्थापन कर सकता है आपके EuclideanDistanceCalculator इस तरह:

std::function<double(Point,Point)> UseEuclidean() { 
    auto obj = std::make_shared<EuclideanDistance>(); 
    return [obj](Point a, Point b)->double { 
    return obj->distance(a, b); 
    }; 
} 
SomeAlgorithm foo(UseEuclidean()); 

लेकिन जैसा कि दूरी कैलकुलेटर शायद ही कभी राज्य की आवश्यकता होती है, तो हम वस्तु के साथ दूर कर सकता है।

std::function<double(Point,Point>> UseEuclidean() { 
    return [obj = std::make_shared<EuclideanDistance>()](Point a, Point b)->double { 
    return obj->distance(a, b); 
    }; 
} 

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

SomeAlgorithm foo([obj = std::make_shared<EuclideanDistance>()](Point a, Point b)->double { 
    return obj->distance(a, b); 
    }); 

लेकिन फिर से, EuclideanDistance नहीं करता

के साथ सी ++ 1 वर्ष समर्थन, यह करने के लिए कम करता है कोई वास्तविक स्थिति नहीं है, इसलिए इसके बजाय हम केवल

std::function<double(Point,Point>> EuclideanDistance() { 
    return [](Point a, Point b)->double { 
    return sqrt((b.x-a.x)*(b.x-a.x) + (b.y-a.y)*(b.y*a.y)); 
    }; 
} 

यदि हमें वास्तव में आंदोलन की आवश्यकता नहीं है अगर हमें राज्य की आवश्यकता है, तो हम unique_function< R(Args...) > प्रकार लिख सकते हैं जो गैर-चाल आधारित असाइनमेंट का समर्थन नहीं करता है, और इसके बजाय उनमें से एक को स्टोर करता है।

इसका मूल यह है कि इंटरफ़ेस DistanceCalculator शोर है। चर का नाम आमतौर पर पर्याप्त है। std::function< double(Point,Point) > m_DistanceCalculator यह है कि यह क्या करता है में स्पष्ट है। टाइप-एरर ऑब्जेक्ट std::function का निर्माता किसी भी आजीवन प्रबंधन के मुद्दों को संभालता है, हम केवल function ऑब्जेक्ट को मूल्य से स्टोर करते हैं।

यदि आपका वास्तविक निर्भरता इंजेक्शन अधिक जटिल है (कई अलग-अलग संबंधित कॉलबैक कहें), इंटरफ़ेस का उपयोग करना बुरा नहीं है। तो

struct InterfaceForDependencyStuff { 
    virtual void method1() = 0; 
    virtual void method2() = 0; 
    virtual int method3(double, char) = 0; 
    virtual ~InterfaceForDependencyStuff() {}; // optional if you want to do more work later, but probably worth it 
}; 

, अपने खुद के make_unique<T>(Args&&...) (एक std एक सी ++ 1 वर्ष में आ रहा है), और इस तरह इसका इस्तेमाल लिख: आप प्रतिलिपि आवश्यकताओं से बचना चाहते हैं, मैं इस के साथ जाना चाहते हैं

इंटरफ़ेस:

SomeAlgorithm(std::unique_ptr<InterfaceForDependencyStuff> pDependencyStuff) 

उपयोग:

SomeAlgorithm foo(std::make_unique<ImplementationForDependencyStuff>(blah blah blah)); 

आप नहीं ०१२३९७९२९६२ चाहते हैंऔर unique_ptr का उपयोग करना चाहते हैं, तो आपको unique_ptr का उपयोग करना होगा जो इसके डिलीटर को स्टोर करता है (एक स्टेटस डिलीटर में गुजरकर)।

दूसरी ओर, यदि std::shared_ptr पहले से ही एक make_shared, और यह डिफ़ॉल्ट रूप से statefully अपने Deleter संग्रहीत करता है के साथ आता है। इसलिए यदि आप अपने इंटरफेस के shared_ptr भंडारण के साथ जाना है, आपको मिलता है:

SomeAlgorithm(std::shared_ptr<InterfaceForDependencyStuff> pDependencyStuff) 

और

SomeAlgorithm foo(std::make_shared<ImplementationForDependencyStuff>(blah blah blah)); 

और make_shared एक सूचक-टू-समारोह है कि ImplementationForDependencyStuff कि खो दिया जा नहीं होगा जब आप परिवर्तित हटाता स्टोर करेगा यह std::shared_ptr<InterfaceForDependencyStuff> पर है, इसलिए आप InterfaceForDependencyStuff में virtual विनाशक की सुरक्षित रूप से कमी कर सकते हैं। मैं व्यक्तिगत रूप से परेशान नहीं होता, और वहां virtual ~InterfaceForDependencyStuff छोड़ देता हूं।

3

ज्यादातर मामलों में आप स्वामित्व हस्तांतरण नहीं चाहते हैं या आवश्यकता नहीं है, यह कोड को समझने में कठोर और कम लचीला बनाता है (स्थानांतरित वस्तुओं से पुन: उपयोग नहीं किया जा सकता है)। विशिष्ट मामले फोन करने वाले के साथ स्वामित्व रखने के लिए होगा:

class SomeAlgorithm { 
    DistanceCalculator* distanceCalculator; 

public: 
    explicit SomeAlgorithm(DistanceCalculator* distanceCalculator_) 
     : distanceCalculator(distanceCalculator_) { 
     if (distanceCalculator == nullptr) { abort(); } 
    } 

    double calculateComplicated() { 
     ... 
     double dist = distanceCalculator->distance(p1, p2); 
     ... 
    } 

    // Default special members are fine. 
}; 

int main() { 
    EuclideanDistanceCalculator distanceCalculator; 
    SomeAlgorithm algorithm(&distanceCalculator); 
    algorithm.calculateComplicated(); 
} 

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

1

किसी भी सूचक (स्मार्ट या कच्चे), या यहां तक ​​कि एक साधारण सी ++ संदर्भ का उपयोग करने का नीचे पक्ष यह है कि वे किसी कॉन्स्ट संदर्भ से गैर-कॉन्स्ट विधियों को कॉल करने की अनुमति देते हैं।

एक भी विधि एक गैर मुद्दा है, और std::function एक अच्छा विकल्प है, लेकिन राज्य या एक से अधिक तरीकों के साथ वर्गों के सामान्य मामले के लिए मैं एक आवरण समान है, लेकिन std::reference_wrapper के समान नहीं प्रस्ताव है कि साथ राज्यविहीन वर्गों के लिए

(जो का अभाव कॉन्स सुरक्षित एक्सेसर)।

template<typename T> 
    struct NonOwningRef{ 
    NonOwningRef() = delete; 
    NonOwningRef(T& other) noexcept : ptr(std::addressof(other)) { }; 
    NonOwningRef(const NonOwningRef& other) noexcept = default; 

    const T& value() const noexcept{ return *ptr; }; 
    T& value() noexcept{ return *ptr; }; 

    private: 
    T* ptr; 
    }; 

उपयोग:

class SomeAlgorithm { 
     NonOwningRef<DistanceCalculator> distanceCalculator; 

    public: 
     SomeAlgorithm(DistanceCalculator& distanceCalculator_) 
     : distanceCalculator(distanceCalculator_) {} 

     double calculateComplicated() { 

     double dist = distanceCalculator.value().distance(p1, p2); 
     return dist; 
    } 
}; 

unique_ptr या shared_ptr साथ T* बदलें संस्करणों के मालिक मिलता है। इस मामले में, किसी भी unique_ptr<T2> या shared_ptr<T2> से निर्माण निर्माण, और निर्माण भी जोड़ें)।

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