सी ++ में यह एक बात है जो मुझे काफी लंबे समय से असहज महसूस कर रही है, क्योंकि मैं ईमानदारी से नहीं जानता कि यह कैसे करना है, भले ही यह सरल लगता है:
मैं 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
या संदर्भ सदस्य चर, - बेस क्लास कंस्ट्रक्टर्स और मेंबर ऑब्जेक्ट कंस्ट्रक्टर्स को आर्ग्युमेंट पास करें।
और शायद कुछ और कमियां भी हो सकती हैं, जिनके बारे में मैं अभी सोच भी नहीं सकता हूं, और मुझे यह भी विशेष रूप से अच्छा नहीं लगता है क्योंकि उपरोक्त बुलेट पॉइंट्स मुझे पहले से ही समझाते हैं।
तो: एक कारखाने को लागू करने के लिए एक अच्छे सामान्य समाधान के करीब भी नहीं।
निष्कर्ष:
हम चाहते हैं कि वस्तुनिष्ठ तात्कालिकता का एक तरीका है:
- आवंटन की परवाह किए बिना एक समान तात्कालिकता के लिए अनुमति दें,
- निर्माण विधियों को अलग, सार्थक नाम दें (इस प्रकार बाय-ओवरलोडिंग पर निर्भर नहीं),
- एक महत्वपूर्ण प्रदर्शन हिट का परिचय न दें और, अधिमानतः, एक महत्वपूर्ण कोड ब्लोट हिट, विशेष रूप से क्लाइंट साइड पर,
- सामान्य हो, जैसा कि: किसी भी वर्ग के लिए पेश किया जाना संभव है।
मेरा मानना है कि मैंने साबित किया है कि मैंने जिन तरीकों का उल्लेख किया है, वे उन आवश्यकताओं को पूरा नहीं करते हैं।
कोई संकेत? कृपया मुझे एक समाधान प्रदान करें, मैं यह नहीं सोचना चाहता कि यह भाषा मुझे इस तरह की तुच्छ अवधारणा को ठीक से लागू करने की अनुमति नहीं देगी।
delete
करते। इस तरह के तरीके पूरी तरह से ठीक हैं, जब तक कि यह "प्रलेखित" है (स्रोत कोड दस्तावेज़ीकरण है; ;-)) जो कॉल करने वाले को सूचक का स्वामित्व लेता है (पढ़ें: उपयुक्त होने पर इसे हटाने के लिए जिम्मेदार है)।
unique_ptr<T>
इसके बजाय वापसी करके बहुत स्पष्ट कर सकते हैं T*
।