C ++ में फ़ैक्टरी विधि पैटर्न को सही तरीके से कैसे लागू किया जाए


329

सी ++ में यह एक बात है जो मुझे काफी लंबे समय से असहज महसूस कर रही है, क्योंकि मैं ईमानदारी से नहीं जानता कि यह कैसे करना है, भले ही यह सरल लगता है:

मैं C ++ में फैक्टरी विधि को सही तरीके से कैसे लागू करूं?

लक्ष्य: ग्राहक को ऑब्जेक्ट के निर्माता के बजाय कारखाने के तरीकों का उपयोग करके किसी वस्तु को तुरंत अस्वीकार करने की अनुमति देना, बिना अस्वीकार्य परिणाम और एक प्रदर्शन हिट।

"फ़ैक्टरी विधि पैटर्न" से मेरा अभिप्राय किसी वस्तु के अंदर स्थिर फैक्ट्री विधियों या किसी अन्य वर्ग या वैश्विक कार्यों में परिभाषित विधियों से है। बस आम तौर पर "निर्माणकर्ता की तुलना में कहीं और कक्षा दसवीं की तात्कालिकता के सामान्य तरीके को पुनर्निर्देशित करने की अवधारणा"।

मुझे कुछ संभावित जवाबों के बारे में बताने की जरूरत है, जिनके बारे में मैंने सोचा है।


०) कारखाने न बनाओ, निर्माता बनाओ।

यह अच्छा लगता है (और वास्तव में अक्सर सबसे अच्छा समाधान), लेकिन एक सामान्य उपाय नहीं है। सबसे पहले, ऐसे मामले हैं जब ऑब्जेक्ट निर्माण एक कार्य जटिल है जो किसी अन्य वर्ग को इसके निष्कर्षण को सही ठहराने के लिए पर्याप्त है। लेकिन यहां तक ​​कि इस तथ्य को एक तरफ रखकर, यहां तक ​​कि साधारण वस्तुओं के लिए भी, जो केवल कंस्ट्रक्टरों का उपयोग नहीं करते हैं।

सबसे सरल उदाहरण मुझे पता है कि 2-डी वेक्टर क्लास है। इतना सरल, फिर भी मुश्किल। मैं कार्टेशियन और ध्रुवीय निर्देशांक दोनों से इसका निर्माण करने में सक्षम होना चाहता हूं। जाहिर है, मैं ऐसा नहीं कर सकता:

struct Vec2 {
    Vec2(float x, float y);
    Vec2(float angle, float magnitude); // not a valid overload!
    // ...
};

मेरी सोच का स्वाभाविक तरीका है:

struct Vec2 {
    static Vec2 fromLinear(float x, float y);
    static Vec2 fromPolar(float angle, float magnitude);
    // ...
};

जो, कंस्ट्रक्टरों के बजाय, मुझे स्थैतिक कारखाने के तरीकों के उपयोग की ओर ले जाता है ... जिसका अनिवार्य रूप से मतलब है कि मैं कारखाने के पैटर्न को लागू कर रहा हूं, किसी तरह ("वर्ग खुद का कारखाना बन जाता है")। यह अच्छा लग रहा है (और इस विशेष मामले के अनुरूप होगा), लेकिन कुछ मामलों में विफल रहता है, जिसका मैं बिंदु 2 में वर्णन करने जा रहा हूं।

एक और मामला: कुछ एपीआई (जैसे कि असंबंधित डोमेन के GUID, या GUID और बिटफील्ड) के दो अपारदर्शी टाइप-बीड द्वारा ओवरलोड करने की कोशिश करना, शब्दार्थ पूरी तरह से अलग हैं (इसलिए - सिद्धांत रूप में - वैध अधिभार हैं, लेकिन जो वास्तव में निकलते हैं एक ही बात - अहस्ताक्षरित ints या शून्य संकेत की तरह।


1) जावा वे

जावा में यह सरल है, क्योंकि हमारे पास केवल गतिशील-आवंटित वस्तुएं हैं। एक कारखाना बनाना उतना ही तुच्छ है:

class FooFactory {
    public Foo createFooInSomeWay() {
        // can be a static method as well,
        //  if we don't need the factory to provide its own object semantics
        //  and just serve as a group of methods
        return new Foo(some, args);
    }
}

C ++ में, इसका अनुवाद इस प्रकार है:

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
};

ठंडा? अक्सर, वास्तव में। लेकिन तब- यह उपयोगकर्ता को केवल गतिशील आवंटन का उपयोग करने के लिए मजबूर करता है। स्टेटिक आवंटन वह है जो C ++ को जटिल बनाता है, लेकिन यह भी है कि अक्सर इसे शक्तिशाली बनाता है। इसके अलावा, मेरा मानना ​​है कि कुछ लक्ष्य (कीवर्ड: एम्बेडेड) मौजूद हैं जो गतिशील आवंटन के लिए अनुमति नहीं देते हैं। और इसका मतलब यह नहीं है कि उन प्लेटफार्मों के उपयोगकर्ता स्वच्छ OOP लिखना पसंद करते हैं।

वैसे भी, दर्शन एक तरफ: सामान्य मामले में, मैं कारखाने के उपयोगकर्ताओं को गतिशील आवंटन के लिए बाध्य नहीं करना चाहता।


2) रिटर्न-बाय-वैल्यू

ठीक है, इसलिए हम जानते हैं कि जब हम गतिशील आवंटन चाहते हैं तो 1) ठंडा होता है। हम उसके ऊपर स्थैतिक आवंटन क्यों नहीं जोड़ेंगे?

class FooFactory {
public:
    Foo* createFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooInSomeWay() {
        return Foo(some, args);
    }
};

क्या? हम वापसी प्रकार द्वारा अधिभार नहीं लगा सकते हैं? ओह, बेशक हम नहीं कर सकते। तो आइए उसको दर्शाने के लिए विधि के नाम बदलें। और हाँ, मैंने ऊपर दिए गए अमान्य कोड उदाहरण को तनाव देने के लिए लिखा है कि मैं विधि के नाम को बदलने की आवश्यकता को कितना नापसंद करता हूं, उदाहरण के लिए क्योंकि हम अब भाषा-अज्ञेय कारखाने के डिजाइन को ठीक से लागू नहीं कर सकते हैं, क्योंकि हमें नाम बदलना है - और इस कोड के प्रत्येक उपयोगकर्ता को विनिर्देश से कार्यान्वयन के उस अंतर को याद रखना होगा।

class FooFactory {
public:
    Foo* createDynamicFooInSomeWay() {
        return new Foo(some, args);
    }
    Foo createFooObjectInSomeWay() {
        return Foo(some, args);
    }
};

ठीक है ... वहाँ हमारे पास है। यह बदसूरत है, क्योंकि हमें विधि का नाम बदलने की आवश्यकता है। यह अपूर्ण है, क्योंकि हमें एक ही कोड को दो बार लिखने की आवश्यकता है। लेकिन एक बार करने के बाद, यह काम करता है। सही?

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

और क्या होगा अगर फू बिल्कुल भी मुकाबला करने योग्य नहीं है? खैर, दोह। ( ध्यान दें कि C ++ 17 में गारंटीकृत कॉपी एलिसन के साथ, ऊपर दिए गए कोड के लिए नहीं-कापी-योग्य कोई समस्या नहीं है )

निष्कर्ष: किसी वस्तु को वापस करके एक कारखाना बनाना वास्तव में कुछ मामलों के लिए एक समाधान है (जैसे कि 2-डी वेक्टर पहले उल्लेख किया गया है), लेकिन अभी भी निर्माणकर्ताओं के लिए सामान्य प्रतिस्थापन नहीं है।


3) दो चरण का निर्माण

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

class Foo {
public:
    Foo() {
        // empty or almost empty
    }
    // ...
};

class FooFactory {
public:
    void createFooInSomeWay(Foo& foo, some, args);
};

void clientCode() {
    Foo staticFoo;
    auto_ptr<Foo> dynamicFoo = new Foo();
    FooFactory factory;
    factory.createFooInSomeWay(&staticFoo);
    factory.createFooInSomeWay(&dynamicFoo.get());
    // ...
}

कोई सोच सकता है कि यह एक आकर्षण की तरह काम करता है। हमारे कोड में केवल एक ही कीमत का भुगतान ...

चूंकि मैंने यह सब लिखा है और इसे अंतिम के रूप में छोड़ दिया है, इसलिए मुझे इसे भी नापसंद करना चाहिए। :) क्यों?

सबसे पहले ... मैं ईमानदारी से दो-चरण निर्माण की अवधारणा को नापसंद करता हूं और जब मैं इसका उपयोग करता हूं तो मैं दोषी महसूस करता हूं। यदि मैं अपनी वस्तुओं को अभिकथन के साथ डिजाइन करता हूं कि "यदि यह मौजूद है, तो यह मान्य स्थिति में है", मुझे लगता है कि मेरा कोड सुरक्षित और कम त्रुटि वाला है। मैं इसे उस तरह चाहता हूं।

उस कन्वेंशन को छोड़ना और अपनी फैक्ट्री के डिजाइन को बदलना सिर्फ इसको बनाने की फैक्ट्री के लिए है।

मुझे पता है कि उपरोक्त कई लोगों को मना नहीं करेंगे, तो चलिए कुछ और ठोस तर्क देते हैं। दो-चरण निर्माण का उपयोग करना, आप नहीं कर सकते:

  • आरंभिक constया संदर्भ सदस्य चर,
  • बेस क्लास कंस्ट्रक्टर्स और मेंबर ऑब्जेक्ट कंस्ट्रक्टर्स को आर्ग्युमेंट पास करें।

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

तो: एक कारखाने को लागू करने के लिए एक अच्छे सामान्य समाधान के करीब भी नहीं।


निष्कर्ष:

हम चाहते हैं कि वस्तुनिष्ठ तात्कालिकता का एक तरीका है:

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

मेरा मानना ​​है कि मैंने साबित किया है कि मैंने जिन तरीकों का उल्लेख किया है, वे उन आवश्यकताओं को पूरा नहीं करते हैं।

कोई संकेत? कृपया मुझे एक समाधान प्रदान करें, मैं यह नहीं सोचना चाहता कि यह भाषा मुझे इस तरह की तुच्छ अवधारणा को ठीक से लागू करने की अनुमति नहीं देगी।


7
@Zac, हालांकि शीर्षक बहुत समान है, वास्तविक प्रश्न IMHO अलग हैं।
Péter Török

2
अच्छा डुप्लिकेट लेकिन इस प्रश्न का पाठ अपने आप में मूल्यवान है।
dmckee --- पूर्व-मध्यस्थ ने

7
यह पूछने के दो साल बाद, मेरे पास जोड़ने के लिए कुछ बिंदु हैं: 1) यह प्रश्न कई डिजाइन पैटर्न ([सार] कारखाने, बिल्डर, आप इसे नाम देते हैं, मुझे उनके वर्गीकरण में पसंद नहीं है ) के लिए प्रासंगिक है। 2) यहां चर्चा की जा रही है कि वास्तविक मुद्दा "ऑब्जेक्ट कंस्ट्रक्शन से ऑब्जेक्ट को कैसे स्टोर करना है?"
कोस

1
@ डेनिस: केवल अगर तुम यह नहीं deleteकरते। इस तरह के तरीके पूरी तरह से ठीक हैं, जब तक कि यह "प्रलेखित" है (स्रोत कोड दस्तावेज़ीकरण है; ;-)) जो कॉल करने वाले को सूचक का स्वामित्व लेता है (पढ़ें: उपयुक्त होने पर इसे हटाने के लिए जिम्मेदार है)।
बोरिस डेलस्टीन

1
@ बोरिस @ डेनिस आप unique_ptr<T>इसके बजाय वापसी करके बहुत स्पष्ट कर सकते हैं T*
कोस

जवाबों:


107

सबसे पहले, ऐसे मामले हैं जब ऑब्जेक्ट निर्माण एक कार्य जटिल है जो किसी अन्य वर्ग को इसके निष्कर्षण को सही ठहराने के लिए पर्याप्त है।

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

Vec2(float x, float y);
Vec2(float angle, float magnitude); // not a valid overload!

इसके लिए एक आसान उपाय है:

struct Cartesian {
  inline Cartesian(float x, float y): x(x), y(y) {}
  float x, y;
};
struct Polar {
  inline Polar(float angle, float magnitude): angle(angle), magnitude(magnitude) {}
  float angle, magnitude;
};
Vec2(const Cartesian &cartesian);
Vec2(const Polar &polar);

केवल नुकसान यह है कि यह थोड़ा सा दिखता है:

Vec2 v2(Vec2::Cartesian(3.0f, 4.0f));

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

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

class Abstract {
  public:
    virtual void do() = 0;
};

class Factory {
  public:
    Abstract *create();
};

Factory f;
Abstract *a = f.create();
a->do();

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


21
कार्टेसियन और ध्रुवीय संरचनाओं के लिए +1। यह आम तौर पर उन वर्गों और संरचनाओं को बनाने के लिए सबसे अच्छा है जो सीधे उस डेटा का प्रतिनिधित्व करते हैं जो वे (सामान्य वीईसी संरचना के विपरीत) के लिए इच्छित हैं। आपका कारखाना एक अच्छा उदाहरण भी है, लेकिन आपका उदाहरण यह नहीं बताता है कि सूचक 'a' का मालिक कौन है। यदि फैक्ट्री 'f' इसका मालिक है, तो संभवत: तब नष्ट हो जाएगा जब 'f' स्कोप को छोड़ देगा, लेकिन यदि 'f' के पास यह नहीं है, तो डेवलपर के लिए यह सुनिश्चित करना जरूरी है कि वह मेमोरी को फ्रीज कर सके या फिर कोई मेमोरी लीक हो सकती है। पाए जाते हैं।
डेविड पीटरसन 19

1
बेशक किसी ऑब्जेक्ट को संदर्भ द्वारा हटाया जा सकता है! देखें stackoverflow.com/a/752699/404734 बेशक यह सवाल उठता है कि क्या संदर्भ के द्वारा डायनामिक मेमोरी वापस करना बुद्धिमानी है, क्योंकि कॉपी द्वारा रिटर्न वैल्यू के संभावित रूप से असाइन किए जाने की समस्या के कारण (कॉलर निश्चित रूप से भी कुछ कर सकता है) जैसे int a = * returnAPoninterToInt () और फिर उसी समस्या का सामना करना पड़ेगा, यदि डायनेमिक रूप से आवंटित स्मृति वापस मिल जाती है, जैसे संदर्भों के लिए, लेकिन सूचक संस्करण में उपयोगकर्ता को स्पष्ट रूप से संदर्भ के लिए भूल जाने के बजाय केवल स्पष्ट रूप से गलतफहमी करना है, गलत होना) ।
कैसरलडी

1
@ Kaiserludi, अच्छी बात है। मैंने ऐसा नहीं सोचा था, लेकिन यह अभी भी "बुराई" करने का तरीका है। मेरे उत्तर को प्रतिबिंबित करने के लिए संपादित किया।
सर्गेई टैचेनोव

विभिन्न गैर-बहुरूपी वर्ग बनाने के बारे में क्या जो अपरिवर्तनीय हैं? एक कारखाने पैटर्न तो C ++ में उपयोग करने के लिए उपयुक्त है?
डेक्सिक्स

@daaxix, आपको एक गैर-बहुरूपी वर्ग के उदाहरण बनाने के लिए कारखाने की आवश्यकता क्यों होगी? मैं यह नहीं देखता कि अपरिवर्तनीयता का इस से क्या लेना-देना है।
सर्गेई टैचेनोव

49

सरल फैक्टरी उदाहरण:

// Factory returns object and ownership
// Caller responsible for deletion.
#include <memory>
class FactoryReleaseOwnership{
  public:
    std::unique_ptr<Foo> createFooInSomeWay(){
      return std::unique_ptr<Foo>(new Foo(some, args));
    }
};

// Factory retains object ownership
// Thus returning a reference.
#include <boost/ptr_container/ptr_vector.hpp>
class FactoryRetainOwnership{
  boost::ptr_vector<Foo>  myFoo;
  public:
    Foo& createFooInSomeWay(){
      // Must take care that factory last longer than all references.
      // Could make myFoo static so it last as long as the application.
      myFoo.push_back(new Foo(some, args));
      return myFoo.back();
    }
};

2
@LokiAstari क्योंकि स्मार्ट पॉइंटर्स का उपयोग मेमोरी पर नियंत्रण को ढीला करने का सबसे सरल तरीका है। जिस पर अन्य भाषाओं की तुलना में C / C ++ लैंग्स का नियंत्रण सर्वोच्च माना जाता है, और जिससे वे सबसे अधिक लाभ प्राप्त करते हैं। इस तथ्य का उल्लेख नहीं करना कि स्मार्ट पॉइंटर्स अन्य प्रबंधित भाषाओं के समान मेमोरी ओवरहेड का उत्पादन करते हैं। यदि आप चाहते हैं कि स्वचालित मेमोरी प्रबंधन की सुविधा जावा या C # में प्रोग्रामिंग शुरू करें, लेकिन उस गड़बड़ को C / C ++ में न डालें।
luke1985

45
@ lukasz1985 unique_ptrउस उदाहरण में प्रदर्शन ओवरहेड नहीं है। स्मृति सहित प्रबंध संसाधन, किसी भी अन्य भाषा पर C ++ के सर्वोच्च लाभ में से एक है क्योंकि आप इसे बिना किसी नियंत्रण के, बिना किसी नियंत्रण के दंडात्मक और नियतकालिक रूप से कर सकते हैं, लेकिन आप इसके ठीक उलट कहते हैं। कुछ लोग चीजों को नापसंद करते हैं C ++ संक्षेप में, स्मार्ट पॉइंटर्स के माध्यम से मेमोरी प्रबंधन की तरह है, लेकिन अगर आप जो चाहते हैं वह सब कुछ स्पष्ट रूप से स्पष्ट होना है, तो C का उपयोग करें; ट्रेडऑफ़ परिमाण कम समस्याओं का आदेश है। मुझे लगता है कि यह अनुचित है आप एक अच्छी सिफारिश को वोट देते हैं।
theCppZoo

1
@ एडमास्टर: मैंने पहले कोई जवाब नहीं दिया क्योंकि वह स्पष्ट रूप से ट्रोल कर रहा था। कृपया ट्रोल को न खिलाएं।
मार्टिन यॉर्क

17
@ लोकीअस्तरी वह एक ट्रोल हो सकता है, लेकिन वह जो कहता है वह लोगों को भ्रमित कर सकता है
TheCppZoo

1
@ य: हाँ। लेकिन: boost::ptr_vector<>यह एक छोटे से अधिक कुशल है क्योंकि यह समझता है कि यह काम को एक उपवर्ग को सौंपने के बजाय सूचक का मालिक है। लेकिन इसका मुख्य लाभ boost::ptr_vector<>यह है कि यह अपने सदस्यों को संदर्भ द्वारा इंगित करता है (सूचक नहीं) इस प्रकार मानक पुस्तकालय में एल्गोरिदम के साथ उपयोग करना वास्तव में आसान है।
मार्टिन

41

क्या आपने किसी कारखाने का उपयोग नहीं करने के बारे में सोचा है, और इसके बजाय टाइप सिस्टम का अच्छा उपयोग कर रहे हैं? मैं दो अलग-अलग तरीकों के बारे में सोच सकता हूं जो इस तरह की बात करते हैं:

विकल्प 1:

struct linear {
    linear(float x, float y) : x_(x), y_(y){}
    float x_;
    float y_;
};

struct polar {
    polar(float angle, float magnitude) : angle_(angle),  magnitude_(magnitude) {}
    float angle_;
    float magnitude_;
};


struct Vec2 {
    explicit Vec2(const linear &l) { /* ... */ }
    explicit Vec2(const polar &p) { /* ... */ }
};

जो आपको चीजें लिखने देता है जैसे:

Vec2 v(linear(1.0, 2.0));

विकल्प 2:

आप "टैग" का उपयोग कर सकते हैं जैसे एसटीएल पुनरावृत्तियों और इस तरह से करता है। उदाहरण के लिए:

struct linear_coord_tag linear_coord {}; // declare type and a global
struct polar_coord_tag polar_coord {};

struct Vec2 {
    Vec2(float x, float y, const linear_coord_tag &) { /* ... */ }
    Vec2(float angle, float magnitude, const polar_coord_tag &) { /* ... */ }
};

यह दूसरा तरीका आपको कोड लिखने देता है जो इस तरह दिखता है:

Vec2 v(1.0, 2.0, linear_coord);

जो आपको प्रत्येक रचनाकार के लिए अद्वितीय प्रोटोटाइप रखने की अनुमति देते हुए भी अच्छा और अभिव्यंजक है।


29

आप इसमें एक बहुत अच्छा समाधान पढ़ सकते हैं: http://www.codeproject.com/Articles/363338/Factory-Pattern-in-Cplusplus

सबसे अच्छा समाधान "टिप्पणियों और चर्चाओं" पर है, "स्थैतिक बनाएँ विधियों की कोई आवश्यकता नहीं है" देखें।

इस विचार से, मैंने एक कारखाना बनाया है। ध्यान दें कि मैं क्यूटी का उपयोग कर रहा हूं, लेकिन आप QD और QString को std समकक्षों के लिए बदल सकते हैं।

#ifndef FACTORY_H
#define FACTORY_H

#include <QMap>
#include <QString>

template <typename T>
class Factory
{
public:
    template <typename TDerived>
    void registerType(QString name)
    {
        static_assert(std::is_base_of<T, TDerived>::value, "Factory::registerType doesn't accept this type because doesn't derive from base class");
        _createFuncs[name] = &createFunc<TDerived>;
    }

    T* create(QString name) {
        typename QMap<QString,PCreateFunc>::const_iterator it = _createFuncs.find(name);
        if (it != _createFuncs.end()) {
            return it.value()();
        }
        return nullptr;
    }

private:
    template <typename TDerived>
    static T* createFunc()
    {
        return new TDerived();
    }

    typedef T* (*PCreateFunc)();
    QMap<QString,PCreateFunc> _createFuncs;
};

#endif // FACTORY_H

नमूना उपयोग:

Factory<BaseClass> f;
f.registerType<Descendant1>("Descendant1");
f.registerType<Descendant2>("Descendant2");
Descendant1* d1 = static_cast<Descendant1*>(f.create("Descendant1"));
Descendant2* d2 = static_cast<Descendant2*>(f.create("Descendant2"));
BaseClass *b1 = f.create("Descendant1");
BaseClass *b2 = f.create("Descendant2");

17

मैं ज्यादातर स्वीकृत उत्तर से सहमत हूं, लेकिन एक C ++ 11 विकल्प है जिसे मौजूदा उत्तरों में शामिल नहीं किया गया है:

  • मूल्य के अनुसार फ़ैक्टरी विधि परिणाम दें , और
  • एक सस्ता कदम निर्माता प्रदान करें ।

उदाहरण:

struct sandwich {
  // Factory methods.
  static sandwich ham();
  static sandwich spam();
  // Move constructor.
  sandwich(sandwich &&);
  // etc.
};

तब आप स्टैक पर ऑब्जेक्ट का निर्माण कर सकते हैं:

sandwich mine{sandwich::ham()};

अन्य चीजों के उप-विषय के रूप में:

auto lunch = std::make_pair(sandwich::spam(), apple{});

या गतिशील रूप से आवंटित:

auto ptr = std::make_shared<sandwich>(sandwich::ham());

मैं इसका उपयोग कब कर सकता हूं?

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

मैं कहता हूं ' हो सकता है ' क्योंकि यह निर्भर करता है कि कौन सा दृष्टिकोण अनावश्यक रूप से अक्षम होने के बिना सबसे स्पष्ट कोड देता है।


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

11

लोकी में एक फैक्ट्री विधि और एक सार फैक्ट्री दोनों हैं । एंडी एलेक्जेंड्रेस्कु द्वारा दोनों को आधुनिक सी ++ डिजाइन में प्रलेखित (बड़े पैमाने पर) किया गया है। फ़ैक्टरी विधि संभवतः आपके पास होने वाली चीज़ों के करीब है, हालांकि यह अभी भी थोड़ा अलग है (कम से कम यदि स्मृति कार्य करती है, तो इसके लिए आवश्यक है कि आप फ़ैक्टरी को उस प्रकार की वस्तुओं को बनाने से पहले एक प्रकार दर्ज कर सकें)।


1
भले ही यह आउट-डेटेड (जो मैं विवाद करता हूं), यह अभी भी पूरी तरह से सेवा योग्य है। मैं अभी भी महान प्रभाव के लिए एक नया C ++ 14 परियोजना में MC ++ D के आधार पर एक कारखाने का उपयोग करता हूं! इसके अलावा, फैक्टरी और सिंगलटन पैटर्न संभवतः सबसे कम आउट-डेटेड भाग हैं। जैसे लोकी के टुकड़े जबकि Functionऔर प्रकार जोड़तोड़ बदला जा सकता है के साथ std::functionऔर <type_traits>और जब तक lambdas, सूत्रण, rvalue refs निहितार्थ है कि कुछ मामूली सुधार करने की आवश्यकता है, तो कारखानों के एकमात्र के लिए कोई मानक प्रतिस्थापन है के रूप में वह उन्हें वर्णन करता है।
धातु

5

मैं अपने सभी सवालों के जवाब देने की कोशिश नहीं करता, क्योंकि मेरा मानना ​​है कि यह बहुत व्यापक है। नोटों की एक जोड़ी:

ऐसे मामले हैं जब ऑब्जेक्ट निर्माण एक कार्य जटिल है जो किसी अन्य वर्ग को इसके निष्कर्षण को सही ठहराने के लिए पर्याप्त है।

वह वर्ग वास्तव में एक कारखाने के बजाय एक बिल्डर है

सामान्य स्थिति में, मैं कारखाने के उपयोगकर्ताओं को गतिशील आवंटन के लिए बाध्य नहीं करना चाहता।

तब आप अपने कारखाने को स्मार्ट पॉइंटर में बदल सकते थे। मेरा मानना ​​है कि इस तरह आप अपना केक बना सकते हैं और इसे खा भी सकते हैं।

यह रिटर्न-बाय-वैल्यू से संबंधित मुद्दों को भी समाप्त करता है।

निष्कर्ष: किसी वस्तु को वापस करके एक कारखाना बनाना वास्तव में कुछ मामलों के लिए एक समाधान है (जैसे कि 2-डी वेक्टर पहले उल्लेख किया गया है), लेकिन अभी भी निर्माणकर्ताओं के लिए सामान्य प्रतिस्थापन नहीं है।

वास्तव में। सभी डिज़ाइन पैटर्न में उनकी (भाषा विशिष्ट) अड़चनें और कमियां हैं। उन्हें केवल तब उपयोग करने की सिफारिश की जाती है जब वे आपकी समस्या को हल करने में मदद करते हैं, न कि अपने स्वयं के लिए।

यदि आप "सही" कारखाने के कार्यान्वयन के बाद हैं, तो अच्छी तरह से, सौभाग्य।


जवाब के लिए धन्यवाद! लेकिन क्या आप बता सकते हैं कि स्मार्ट पॉइंटर के इस्तेमाल से डायनेमिक एलोकेशन का प्रतिबंध कैसे जारी होगा? मुझे यह हिस्सा नहीं मिला।
कोस

@Kos, स्मार्ट पॉइंटर्स के साथ आप अपने उपयोगकर्ताओं से वास्तविक वस्तु के आवंटन / सौदे को छिपा सकते हैं। वे केवल इनकैप्सुलेटिंग स्मार्ट पॉइंटर देखते हैं, जो कि बाहरी दुनिया को एक स्टैटिकली एलोकेटेड ऑब्जेक्ट की तरह व्यवहार करता है।
पेटर तोर्क

@Kos, सख्त अर्थों में नहीं, AFAIR। आप लपेटे जाने वाली वस्तु में गुजरते हैं, जिसे आपने किसी बिंदु पर गतिशील रूप से आवंटित किया है। फिर स्मार्ट पॉइंटर इसका स्वामित्व लेता है और यह सुनिश्चित करता है कि यह तब ठीक से नष्ट हो जाए जब इसकी अधिक आवश्यकता न हो (अलग-अलग प्रकार के स्मार्ट पॉइंटर्स के लिए अलग-अलग समय तय किया जाता है)।
पेटर तोरॉक

3

यह मेरा c ++ 11 स्टाइल सॉल्यूशन है। पैरामीटर 'आधार' सभी उप-वर्गों के आधार वर्ग के लिए है। निर्माता, उप-वर्ग उदाहरण बनाने के लिए std :: फ़ंक्शन ऑब्जेक्ट हैं, आपके उप-क्लास 'स्थिर सदस्य फ़ंक्शन' (कुछ args) बनाने के लिए बाध्यकारी हो सकते हैं। यह शायद सही नहीं है, लेकिन मेरे लिए काम करता है। और यह थोड़े 'सामान्य' समाधान है।

template <class base, class... params> class factory {
public:
  factory() {}
  factory(const factory &) = delete;
  factory &operator=(const factory &) = delete;

  auto create(const std::string name, params... args) {
    auto key = your_hash_func(name.c_str(), name.size());
    return std::move(create(key, args...));
  }

  auto create(key_t key, params... args) {
    std::unique_ptr<base> obj{creators_[key](args...)};
    return obj;
  }

  void register_creator(const std::string name,
                        std::function<base *(params...)> &&creator) {
    auto key = your_hash_func(name.c_str(), name.size());
    creators_[key] = std::move(creator);
  }

protected:
  std::unordered_map<key_t, std::function<base *(params...)>> creators_;
};

उपयोग पर एक उदाहरण।

class base {
public:
  base(int val) : val_(val) {}

  virtual ~base() { std::cout << "base destroyed\n"; }

protected:
  int val_ = 0;
};

class foo : public base {
public:
  foo(int val) : base(val) { std::cout << "foo " << val << " \n"; }

  static foo *create(int val) { return new foo(val); }

  virtual ~foo() { std::cout << "foo destroyed\n"; }
};

class bar : public base {
public:
  bar(int val) : base(val) { std::cout << "bar " << val << "\n"; }

  static bar *create(int val) { return new bar(val); }

  virtual ~bar() { std::cout << "bar destroyed\n"; }
};

int main() {
  common::factory<base, int> factory;

  auto foo_creator = std::bind(&foo::create, std::placeholders::_1);
  auto bar_creator = std::bind(&bar::create, std::placeholders::_1);

  factory.register_creator("foo", foo_creator);
  factory.register_creator("bar", bar_creator);

  {
    auto foo_obj = std::move(factory.create("foo", 80));
    foo_obj.reset();
  }

  {
    auto bar_obj = std::move(factory.create("bar", 90));
    bar_obj.reset();
  }
}

मुझे अच्छा लग रहा है। आप (शायद कुछ स्थूल जादू) स्थैतिक पंजीकरण कैसे लागू करेंगे? बस आधार वर्ग की कल्पना कीजिए कि वह वस्तुओं के लिए कुछ सर्विसिंग क्लास है। व्युत्पन्न वर्ग उन वस्तुओं को एक विशेष प्रकार की सर्विसिंग प्रदान करते हैं। और आप प्रत्येक प्रकार की सेवाओं के लिए आधार से प्राप्त वर्ग को जोड़कर उत्तरोत्तर विभिन्न प्रकार की सेवाओं को जोड़ना चाहते हैं।
17

2

फैक्टरी पैटर्न

class Point
{
public:
  static Point Cartesian(double x, double y);
private:
};

और यदि आप संकलक रिटर्न वैल्यू ऑप्टिमाइज़ेशन का समर्थन नहीं करते हैं, तो इसे खोदें, यह संभवतः बहुत अधिक अनुकूलन नहीं करता है ...


क्या इसे वास्तव में कारखाना पैटर्न का कार्यान्वयन माना जा सकता है?
डेनिस

1
@ डेनिस: एक पतित मामले के रूप में, मुझे ऐसा लगता है। इसके साथ समस्या Factoryयह है कि यह काफी सामान्य है और बहुत सारी जमीन को कवर करता है; एक कारखाने तर्क जोड़ सकते हैं (पर्यावरण / सेटअप के आधार पर) या उदाहरण के लिए कुछ कैशिंग (फ्लाईवेट / पूल से संबंधित) प्रदान करते हैं, लेकिन ये मामले केवल कुछ स्थितियों में समझ में आते हैं।
Matthieu M.

यदि केवल संकलक बदलना उतना ही आसान होगा जितना कि आप इसे ध्वनि बनाते हैं :)
rozina

@rozina: :) यह लिनक्स में अच्छी तरह से काम करता है (gcc / clang उल्लेखनीय रूप से संगत हैं); मैं मानता हूं कि विंडोज अभी भी अपेक्षाकृत बंद है, हालांकि इसे 64-बिट प्लेटफॉर्म पर बेहतर तरीके से प्राप्त करना चाहिए (यदि मुझे सही तरीके से याद है तो रास्ते में कम पेटेंट)।
Matthieu M.

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

1

मुझे पता है कि इस सवाल का जवाब 3 साल पहले दिया जा चुका है, लेकिन यह वही हो सकता है जो आप खोज रहे थे।

Google ने कुछ हफ़्ते पहले एक लाइब्रेरी जारी की है जिसमें आसान और लचीली गतिशील ऑब्जेक्ट आवंटन की अनुमति दी गई है। यहाँ यह है: http://google-opensource.blogspot.fr/2014/01/introducing-infact-library.html

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.