C ++: अपनी निर्भरता को कक्षा में रखना चाहिए या उसका निरीक्षण करना चाहिए?


16

कहो कि मेरे पास एक वर्ग है Foobarजो उपयोग करता है (निर्भर करता है) वर्ग Widget। अच्छे राजभाषा दिनों में, Widgetवूलुद को एक क्षेत्र के रूप में Foobar, या शायद एक स्मार्ट सूचक के रूप में घोषित किया जा सकता है यदि बहुरूपी व्यवहार की आवश्यकता होती है, और इसे निर्माणकर्ता में आरंभ किया जाएगा:

class Foobar {
    Widget widget;
    public:
    Foobar() : widget(blah blah blah) {}
    // or
    std::unique_ptr<Widget> widget;
    public:
    Foobar() : widget(std::make_unique<Widget>(blah blah blah)) {}
    (…)
};

और हम सब सेट और किया जाएगा। दुर्भाग्य से, आज, जावा बच्चे इसे देखकर एक बार हम पर हँसेंगे, और सही रूप में, जैसा कि यह युगल Foobarऔर Widgetएक साथ है। समाधान सरल प्रतीत होता है: निर्भरता के निर्माण को Foobarकक्षा से बाहर करने के लिए निर्भरता इंजेक्शन लागू करें । लेकिन फिर, C ++ हमें निर्भरता के स्वामित्व के बारे में सोचने के लिए मजबूर करता है। तीन समाधान दिमाग में आते हैं:

अद्वितीय सूचक

class Foobar {
    std::unique_ptr<Widget> widget;
    public:
    Foobar(std::unique_ptr<Widget> &&w) : widget(w) {}
    (…)
}

FoobarWidgetउस पर एकमात्र स्वामित्व का दावा किया जाता है। इसके निम्नलिखित फायदे हैं:

  1. प्रदर्शन पर प्रभाव नगण्य है।
  2. यह सुरक्षित है, क्योंकि यह Foobarजीवनकाल को नियंत्रित करता है Widget, इसलिए यह सुनिश्चित करता है कि Widgetअचानक गायब न हो।
  3. यह सुरक्षित है कि Widgetरिसाव नहीं होगा और जब जरूरत नहीं होगी तो ठीक से नष्ट हो जाएगा।

हालाँकि, यह लागत पर आता है:

  1. यह इस बात पर प्रतिबंध लगाता है कि किस तरह से Widgetउदाहरणों का उपयोग किया जा सकता है, जैसे कोई स्टैक-आबंटित नहीं किया Widgetsजा सकता है, कोई Widgetसाझा नहीं किया जा सकता है।

साझा किया गया सूचक

class Foobar {
    std::shared_ptr<Widget> widget;
    public:
    Foobar(const std::shared_ptr<Widget> &w) : widget(w) {}
    (…)
}

यह संभवतः निकटतम समकक्ष ओ.टी. जावा और अन्य कचरा एकत्र की गई भाषाएँ हैं। लाभ:

  1. अधिक सार्वभौमिक, क्योंकि यह निर्भरता को साझा करने की अनुमति देता है।
  2. unique_ptrसमाधान की सुरक्षा (अंक 2 और 3) बनाए रखता है ।

नुकसान:

  1. कोई साझाकरण शामिल नहीं होने पर संसाधनों को बर्बाद करता है।
  2. अभी भी ढेर आवंटन की आवश्यकता है और स्टैक-आवंटित वस्तुओं को रोकना है।

सादा ओएल 'का सूचक

class Foobar {
    Widget *widget;
    public:
    Foobar(Widget *w) : widget(w) {}
    (…)
}

कच्चे पॉइंटर को कक्षा के अंदर रखें और स्वामित्व का बोझ किसी और पर स्थानांतरित करें। पेशेवरों:

  1. यह जितना आसान हो सकता है।
  2. यूनिवर्सल, बस किसी भी स्वीकार करता है Widget

विपक्ष:

  1. अब सुरक्षित नहीं है।
  2. एक और इकाई का परिचय देता है जो दोनों के स्वामित्व के लिए जिम्मेदार है Foobarऔर Widget

कुछ पागल टेम्पलेट मेटाप्रोग्रामिंग

केवल एक ही लाभ जो मैं सोच सकता हूं कि मैं उन सभी पुस्तकों को पढ़ पाऊंगा, जिनके लिए मुझे समय नहीं मिला, जबकि मेरा सॉफ्टवेयर बन रहा है;)

मैं तीसरे समाधान की ओर झुकता हूं, क्योंकि यह सबसे सार्वभौमिक है, कुछ Foobarsभी वैसे भी प्रबंधित करना है, इसलिए प्रबंधन Widgetsसरल परिवर्तन है। हालांकि, कच्चे पॉइंटर्स का उपयोग मुझे परेशान करता है, दूसरी ओर स्मार्ट पॉइंटर समाधान मुझे गलत लगता है, क्योंकि वे निर्भरता के उपभोक्ता को प्रतिबंधित करते हैं कि निर्भरता कैसे बनाई जाती है।

क्या मैं कुछ भूल रहा हूँ? या सिर्फ C ++ में निर्भरता इंजेक्शन तुच्छ नहीं है? क्या अपनी निर्भरता को स्वयं वर्गीकृत करना चाहिए या केवल उनका निरीक्षण करना चाहिए?


1
प्रारंभ से ही, यदि आप C ++ 11 और / या के तहत संकलन कर सकते हैं, तो कक्षा में संसाधनों को संभालने के तरीके के रूप में सादे पॉइंटर का उपयोग करना अनुशंसित तरीका नहीं है। अगर फ़ोबार वर्ग संसाधन का एकमात्र मालिक है और संसाधन को मुक्त किया जाना चाहिए, जब फ़ोबार कार्यक्षेत्र से बाहर हो जाता है, तो std::unique_ptrजाने का रास्ता है। आप std::move()संसाधन के स्वामित्व को ऊपरी दायरे से कक्षा में स्थानांतरित करने के लिए उपयोग कर सकते हैं ।
एंडी

1
मुझे कैसे पता चलेगा कि Foobarएकमात्र मालिक है? पुराने मामले में यह सरल है। लेकिन डीआई के साथ समस्या, जैसा कि मैं इसे देखता हूं, जैसे कि यह अपनी निर्भरता के निर्माण से वर्ग को डिकॉय करता है, यह उन निर्भरता के स्वामित्व से भी डिकॉय करता है (जैसा कि स्वामित्व निर्माण से बंधा है)। जावा जैसे कचरा एकत्रित वातावरण में, यह कोई समस्या नहीं है। C ++ में, यह है।
el.pescado

1
@ el.pescado जावा में भी यही समस्या है, मेमोरी एकमात्र संसाधन नहीं है। उदाहरण के लिए फाइलें और कनेक्शन भी हैं। जावा में इसे हल करने का सामान्य तरीका एक कंटेनर है जो इसके सभी निहित वस्तुओं के जीवनचक्र का प्रबंधन करता है। इसलिए आपको डीआई कंटेनर में रखी वस्तुओं के बारे में चिंता करने की ज़रूरत नहीं है। यह c ++ एप्लिकेशन के लिए भी काम कर सकता है यदि DI कंटेनर के लिए एक तरीका है जिससे यह पता चल सके कि किस जीवन चक्र के चरण में कब स्विच करना है।
स्पेसट्रैकर

3
बेशक, आप इसे बिना किसी जटिलता के पुराने तरीके से कर सकते हैं और इसमें ऐसे कोड हैं जो काम करते हैं और बनाए रखने के लिए आसान है। (ध्यान दें कि जावा लड़के हँस सकते हैं, लेकिन वे हमेशा के बारे में
मना

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

जवाबों:


2

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

मुझे कैसे पता चलेगा कि Foobarएकमात्र मालिक है? पुराने मामले में यह सरल है। लेकिन डीआई के साथ समस्या, जैसा कि मैं इसे देखता हूं, जैसे कि यह अपनी निर्भरता के निर्माण से वर्ग को डिकॉय करता है, यह उन निर्भरता के स्वामित्व से भी डिकॉय करता है (जैसा कि स्वामित्व निर्माण से बंधा है)। जावा जैसे कचरा एकत्रित वातावरण में, यह कोई समस्या नहीं है। C ++ में, यह है।

चाहे आप का उपयोग करें std::unique_ptr<Widget>या std::shared_ptr<Widget>, कि आप को तय करना है और अपनी कार्यक्षमता से आता है।

मान लें कि आपके पास एक है Utilities::Factory, जो आपके ब्लॉकों के निर्माण के लिए जिम्मेदार है, जैसे कि Foobar। DI सिद्धांत के बाद, आपको Widgetउदाहरण की आवश्यकता होगी , इसे Foobar's' कंस्ट्रक्टर के उपयोग से इंजेक्ट करने के लिए, जो कि किसी एक Utilities::Factoryविधि के अंदर है , उदाहरण के लिए createWidget(const std::vector<std::string>& params), आप विजेट बनाएँ और Foobarऑब्जेक्ट में इंजेक्ट करें ।

अब आपके पास एक Utilities::Factoryविधि है, जिसने Widgetवस्तु बनाई है । क्या इसका मतलब है, विधि मुझे इसके विलोपन के लिए जिम्मेदार होना चाहिए? बिलकूल नही। यह केवल आपको उदाहरण बनाने के लिए है।


आइए कल्पना करें, आप एक एप्लिकेशन विकसित कर रहे हैं, जिसमें कई विंडो होंगी। प्रत्येक विंडो को Foobarकक्षा का उपयोग करके दर्शाया जाता है , इसलिए वास्तव में Foobarएक नियंत्रक की तरह कार्य करता है।

नियंत्रक शायद आपके कुछ एस का उपयोग करेगा Widgetऔर आपको खुद से पूछना होगा:

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

std::shared_ptr<Widget> जाने का रास्ता है।

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

यही कारण है कि जहां है std::unique_ptr<Widget>अपने सिंहासन का दावा करने के लिए आता है।


अपडेट करें:

मैं वास्तव में @DominicMcDonnell के साथ आजीवन मुद्दे पर सहमत नहीं हूं । कॉलिंग std::moveपर std::unique_ptrपूरी तरह से स्वामित्व हस्तांतरित कर देता है, तो भले ही आप एक बनाने के object Aलिए एक विधि में और दूसरे के लिए इसे पारित object Bएक निर्भरता के रूप में, object Bअब के संसाधन के लिए जिम्मेदार होगा object Aऔर सही ढंग से हटा देंगे, जब object Bक्षेत्र से बाहर चला जाता है।


स्वामित्व और स्मार्ट संकेत का सामान्य विचार मेरे लिए स्पष्ट है। यह सिर्फ मुझे DI के विचार के साथ अच्छा खेलने के लिए प्रतीत नहीं होता है। निर्भरता इंजेक्शन मेरे लिए, अपनी निर्भरता से वर्ग को हटाने के बारे में है, फिर भी इस मामले में कक्षा अभी भी प्रभावित करती है कि इसकी निर्भरता कैसे बनाई जाती है।
el.pescado

उदाहरण के लिए, मेरे पास जो समस्या है, unique_ptrवह इकाई परीक्षण है (DI को परीक्षण-अनुकूल के रूप में विज्ञापित किया गया है): मैं परीक्षण करना चाहता हूं Foobar, इसलिए मैं Widgetइसे बनाता हूं , इसे पास करता हूं Foobar, व्यायाम करता Foobarहूं और फिर निरीक्षण करना चाहता हूं Widget, लेकिन जब तक Foobarकिसी तरह इसे उजागर नहीं किया जाता, मैं कर सकता हूं 'क्योंकि यह दावा किया गया है Foobar
el.pescado

@ el.pescado आप दो चीजों को एक साथ मिला रहे हैं। आप पहुंच और स्वामित्व का मिश्रण कर रहे हैं। सिर्फ इसलिए Foobarकि संसाधन का मतलब यह नहीं है, किसी और को इसका उपयोग नहीं करना चाहिए। आप Foobarइस तरह की एक विधि लागू कर सकते हैं Widget* getWidget() const { return this->_widget.get(); }, जो आपके द्वारा काम करने वाले कच्चे सूचक को वापस कर देगी। आप इस विधि का उपयोग अपनी इकाई परीक्षणों के इनपुट के रूप में कर सकते हैं, जब आप Widgetकक्षा का परीक्षण करना चाहते हैं ।
एंडी

अच्छी बात है, यह समझ में आता है।
el.pescado

1
अगर आपने एक shared_ptr को एक unique_ptr में बदल दिया, तो आप unique_ness की गारंटी कैसे देना चाहेंगे ???
aconcagua

2

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

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

अपडेट करें

डेविड पैकर की प्रतिक्रिया ने मुझे सवाल के बारे में थोड़ा और सोचने पर मजबूर कर दिया। मेरे अनुभव में साझा निर्भरता के लिए मूल उत्तर सही है, जो निर्भरता इंजेक्शन के फायदों में से एक है, आपके पास एक निर्भरता के एक उदाहरण का उपयोग करके कई उदाहरण हो सकते हैं। हालांकि अगर आपकी कक्षा को किसी विशेष निर्भरता का अपना उदाहरण होना चाहिए, तो std::unique_ptrसही उत्तर है।


1

सबसे पहले - यह सी ++ है, जावा नहीं - और यहां कई चीजें अलग-अलग होती हैं। जावा के लोगों को स्वामित्व के बारे में कोई समस्या नहीं है क्योंकि स्वचालित कचरा संग्रह है जो उनके लिए हल करता है।

दूसरा: इस सवाल का कोई सामान्य जवाब नहीं है - यह इस बात पर निर्भर करता है कि आवश्यकताएं क्या हैं!

FooBar और विजेट युग्मन के बारे में क्या समस्या है? FooBar एक विजेट का उपयोग करना चाहता है, और अगर हर FooBar उदाहरण हमेशा अपने और एक ही विजेट होगा वैसे भी, इसे छोड़ दो ...

C ++ में, आप "अजीब" चीजें भी कर सकते हैं जो कि केवल जावा में मौजूद नहीं हैं, उदाहरण के लिए एक वैरेडिक टेम्प्लेट कंस्ट्रक्टर है (ठीक है, वहां मौजूद है ... जावा में एक नोटेशन, जिसका उपयोग कंस्ट्रक्टरों में भी किया जा सकता है, लेकिन यह है कि एक वस्तु सरणी को छिपाने के लिए सिंटैक्टिक शुगर, जिसका वास्तव में वास्तविक वैरेडिक टेम्प्लेट से कोई लेना-देना नहीं है!) - श्रेणी 'कुछ पागल टेम्पलेट मेटाप्रोग्रामिंग':

namespace WidgetFactory
{
    Widget* create(int, int)
    {
        return 0;
    }
    Widget* create(int, bool, long)
    {
        return 0;
    }
}
class FooBar
{
public:
    template < typename ...Arguments >
    FooBar(Arguments... arguments)
        : mWidget(WidgetFactory::create(arguments...))
    {
    }
    ~FooBar()
    {
        delete mWidget;
    }
private:
    Widget* mWidget;
};

FooBar foobar1(10, 12);
FooBar foobar2(51, true, 54L);

पसंद है?

बेशक, वहाँ रहे हैं जैसे अगर आप एक समय में दूर से पहले किसी भी FooBar उदाहरण मौजूद है, अगर आप चाहते हैं या विजेट, ..., या बस का पुन: उपयोग करने की आवश्यकता पर विजेट बनाने की जरूरत है - कारणों जब आप चाहते हैं या दोनों वर्गों दसगुणा करने की आवश्यकता क्योंकि वर्तमान समस्या के लिए यह अधिक उपयुक्त है (उदाहरण के लिए यदि विजेट एक GUI तत्व है और FooBar / एक नहीं हो सकता है / हो सकता है)।

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


1
C ++ में, classकुछ भी नहीं, लेकिन स्टैटिक विधियों के साथ उपयोग न करें , भले ही यह आपके उदाहरण के अनुसार केवल एक कारखाना हो। जो OO सिद्धांतों का उल्लंघन करता है। इसके बजाय नामस्थानों का उपयोग करें और उन्हें उपयोग करके अपने कार्यों को समूहित करें।
एंडी

@DavidPacker हम्म, निश्चित रूप से, आप सही कह रहे हैं - मैंने चुपचाप क्लास को कुछ निजी इंटर्नल मान लिया है, लेकिन यह न तो कहीं दिखाई देता है और न ही कहीं और उल्लेख किया गया है ...
एकॉनगुआ

1

आपको कम से कम दो और विकल्प याद आ रहे हैं जो C ++ में आपके लिए उपलब्ध हैं:

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

एक और है एक गहरी प्रतिलिपि का उपयोग करके मूल्य द्वारा बहुरूपी वस्तुओं को लेना। ऐसा करने का पारंपरिक तरीका एक आभासी क्लोन विधि का उपयोग कर रहा है, एक और विकल्प जो लोकप्रिय हो गया है वह है पॉलीमॉर्फिक रूप से व्यवहार करने वाले मूल्य प्रकार बनाने के लिए टाइप इरेज़र का उपयोग करना।

कौन सा विकल्प सबसे उपयुक्त है यह वास्तव में आपके उपयोग के मामले पर निर्भर करता है, सामान्य उत्तर देना कठिन है। यदि आपको केवल स्थैतिक बहुरूपता की आवश्यकता है, तो मैं कहूंगा कि टेम्पलेट जाने के लिए सबसे C ++ तरीका है।


0

आपने चौथे संभावित उत्तर को नजरअंदाज कर दिया है, जो निर्भरता इंजेक्शन के साथ आपके द्वारा पोस्ट किए गए पहले कोड (मूल्य से एक सदस्य को संग्रहीत करने के "अच्छे ol 'दिन) को जोड़ती है:

class Foobar {
    Widget widget;
public:
    Foobar(Widget w) // pass w by value
     : widget{ std::move(w) } {}
};

ग्राहक कोड तब लिख सकते हैं:

Widget w;
Foobar f1{ w }; // default: copy w into f1
Foobar f2{ std::move(w) }; // move w into f2

वस्तुओं के बीच युग्मन का प्रतिनिधित्व (विशुद्ध रूप से) आपके द्वारा सूचीबद्ध मानदंडों पर नहीं किया जाना चाहिए (जो कि विशुद्ध रूप से "सुरक्षित जीवनकाल प्रबंधन के लिए बेहतर है") पर आधारित नहीं है।

आप वैचारिक मापदंड का भी उपयोग कर सकते हैं ("एक कार में चार पहिए होते हैं" बनाम "एक कार चार पहिए का उपयोग करती है जिसे चालक को साथ लाना होता है")।

आपके पास अन्य API द्वारा लगाए गए मानदंड हो सकते हैं (यदि आपको एपीआई से जो मिलता है वह एक कस्टम आवरण या std है: unique_ptr उदाहरण के लिए, आपके क्लाइंट कोड में विकल्प भी सीमित हैं)।


1
यह बहुरूपी व्यवहार को रोकता है, जो अतिरिक्त रूप से मूल्य को सीमित करता है।
el.pescado 11

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