c++ - सी++ में कारखाने विधि पैटर्न को सही तरीके से कैसे कार्यान्वित करें




design-patterns idioms (7)

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

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

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));

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

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

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

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

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

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

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

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

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

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

मुझे कुछ संभावित उत्तरों के माध्यम से स्कीम करने दो जो मैंने सोचा है।

0) कारखानों को न बनाएं, रचनाकार बनाएं।

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

मुझे पता है कि सबसे सरल उदाहरण 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) के दो अपारदर्शी टाइपपीफ द्वारा अधिभारित करने का प्रयास, अर्थात् पूरी तरह से अलग-अलग प्रकार (इसलिए - सिद्धांत में - मान्य अधिभार) लेकिन जो वास्तव में बाहर निकलता है एक ही बात - जैसे हस्ताक्षरित इंट्स या शून्य पॉइंटर्स।

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);
    }
}

सी ++ में, यह अनुवाद करता है:

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

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

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

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);
    }
};

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

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

और क्या होगा यदि फू बिल्कुल मुकाबला नहीं है? खैर, दोह। ( ध्यान दें कि सी ++ 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 या संदर्भ सदस्य चर,
  • बेस क्लास कन्स्ट्रक्टर और सदस्य ऑब्जेक्ट कन्स्ट्रक्टर को तर्क पास करें।

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

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

निष्कर्ष:

हम ऑब्जेक्ट इंस्टेंटेशन का एक तरीका चाहते हैं जो:

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

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

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


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

// 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();
    }
};

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

विकल्प 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);

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


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

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


मैं ज्यादातर स्वीकार किए गए उत्तर से सहमत हूं, लेकिन एक सी ++ 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());

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

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

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


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

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();
  }
}

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

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

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





factory-method