कंस्ट्रक्टर को आमतौर पर तरीकों को नहीं कहना चाहिए


12

मैंने एक सहकर्मी से वर्णन किया कि क्यों एक निर्माता एक विधि को कॉल करने वाला एक एंटीपैटर्न हो सकता है।

उदाहरण (मेरी रस्टी C ++ में)

class C {
public :
    C(int foo);
    void setFoo(int foo);
private:
    int foo;
}

C::C(int foo) {
    setFoo(foo);
}

void C::setFoo(int foo) {
    this->foo = foo
}

मैं आपके अतिरिक्त योगदान के माध्यम से इस तथ्य को बेहतर ढंग से प्रेरित करना चाहूंगा। यदि आपके पास उदाहरण, पुस्तक संदर्भ, ब्लॉग पृष्ठ या सिद्धांतों के नाम हैं, तो उनका बहुत स्वागत होगा।

संपादित करें: मैं सामान्य रूप से बात कर रहा हूं, लेकिन हम अजगर में कोडिंग कर रहे हैं।


क्या यह एक सामान्य नियम या विशिष्ट भाषाओं के लिए विशिष्ट है?
ChrisF

कौनसी भाषा? C ++ में यह एक एंटी-पैटर्न से अधिक है: parashift.com/c+-faq-lite/strange-inheritance.html#faq-23.5
LennProgrammers

@ Lenny222, ओपी "क्लास तरीकों" के बारे में बात करता है, जो - मेरे लिए कम से कम - गैर-इंस्टेंस तरीकों का मतलब है । इसलिए यह आभासी नहीं हो सकता।
पेटर तोर्क

3
@ एएलबी जावा में यह पूरी तरह से ठीक है। यद्यपि आप क्या नहीं करना चाहिए, स्पष्ट रूप thisसे निर्माणकर्ता से आपके द्वारा कॉल किए गए किसी भी तरीके को पास कर सकते हैं।
biziclop

3
@Stefano Borini: यदि आप पायथन में कोडिंग कर रहे हैं, तो रस्टी C ++ के बजाय पायथन में उदाहरण क्यों नहीं दिखा? इसके अलावा, कृपया बताएं कि यह एक बुरी बात क्यों है। हम यह हर समय करते है।
S.Lott

जवाबों:


26

आपने कोई भाषा निर्दिष्ट नहीं की है।

C ++ में एक वर्चुअल फ़ंक्शन को कॉल करते समय एक कंस्ट्रक्टर को सावधान रहना चाहिए, उस वास्तविक फ़ंक्शन को कॉल करना क्लास कार्यान्वयन है। यदि यह एक कार्यान्वयन के बिना एक शुद्ध आभासी विधि है, तो यह एक एक्सेस उल्लंघन होगा।

एक निर्माता गैर-आभासी कार्यों को कॉल कर सकता है।

यदि आपकी भाषा जावा है जहाँ फ़ंक्शन आम तौर पर डिफ़ॉल्ट रूप से आभासी होते हैं तो यह समझ में आता है कि आपको अतिरिक्त सावधानी बरतनी होगी।

C # उस स्थिति को संभालने के लिए लगता है जिस तरह से आप अपेक्षा करते हैं: आप निर्माणकर्ताओं में आभासी तरीकों को कॉल कर सकते हैं और यह सबसे अंतिम संस्करण कहता है। इसलिए C # में एंटी-पैटर्न नहीं।

कंस्ट्रक्टरों से कॉल करने के तरीकों का एक सामान्य कारण यह है कि आपके पास कई ऐसे कंस्ट्रक्टर हैं जो कॉमन "इनिट" विधि को कॉल करना चाहते हैं।

ध्यान दें कि विध्वंसक वर्चुअल विधियों के साथ एक ही समस्या होगी, इस प्रकार आपके पास एक वर्चुअल "क्लीनअप" विधि नहीं हो सकती है जो आपके विध्वंसक के बाहर बैठता है और यह बेस-क्लास डिस्ट्रक्टर द्वारा कॉल करने की अपेक्षा करता है।

जावा और सी # में विध्वंसक नहीं हैं, उनके पास अंतिम रूप हैं। मैं जावा के साथ व्यवहार नहीं जानता।

सी # इस संबंध में सफाई को सही ढंग से संभालने के लिए प्रकट होता है।

(ध्यान दें कि हालांकि जावा और C # में कचरा संग्रह है, जो केवल मेमोरी आवंटन का प्रबंधन करता है। अन्य सफाई है कि आपके विनाशकर्ता को ऐसा करने की आवश्यकता है जो मेमोरी को रिलीज़ नहीं कर रहा है)।


13
यहां कुछ छोटी त्रुटियां हैं। C # में विधियां डिफ़ॉल्ट रूप से आभासी नहीं हैं। सी # में एक निर्माता में एक आभासी विधि को कॉल करने पर सी ++ की तुलना में अलग शब्दार्थ है; सबसे अधिक व्युत्पन्न प्रकार पर वर्चुअल विधि को बुलाया जाएगा, न कि वर्तमान में बनाए जा रहे प्रकार के हिस्से पर वर्चुअल विधि। C # इसके अंतिमकरण के तरीकों को "विध्वंसक" कहता है, लेकिन आप सही हैं कि उनके पास फाइनल के शब्दार्थ हैं। C # डिस्ट्रक्टर्स में वर्चुअल तरीके उसी तरह से काम करते हैं जैसे वे कंस्ट्रक्टर में करते हैं; सबसे व्युत्पन्न विधि कहा जाता है।
एरिक लिपर्ट

@ पैटर: मेरा इरादा उदाहरण के तरीकों से है। गलतफहमी के लिए खेद है।
स्टेफानो बोरीनी

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

5
आपका स्वागत है। ध्यान दें कि C # में बेस क्लास कंस्ट्रक्टर में वर्चुअल मेथड को कॉल करना अभी भी एक बहुत बुरा विचार है।
एरिक लिपर्ट

यदि आप एक निर्माता से जावा में एक (आभासी) विधि कहते हैं, तो यह हमेशा सबसे अधिक व्युत्पन्न ओवरराइड आह्वान करेगा। हालाँकि, जिसे आप "जिस तरह से आप उम्मीद करेंगे" वह है जिसे मैं भ्रामक कहूँगा। क्योंकि जब जावा सबसे अधिक व्युत्पन्न ओवरराइड का आह्वान करता है, तो यह विधि केवल आरंभिक रूप से संसाधित किए गए फाइलरों को देखेगी, लेकिन अपने स्वयं के वर्ग चलाने के लिए नहीं। किसी वर्ग पर ऐसी विधि लागू करना जिसके पास अभी तक स्थापित नहीं किया गया है, खतरनाक हो सकता है। इसलिए मुझे लगता है कि C ++ ने यहां बेहतर विकल्प बनाया।
5gon12eder

18

ठीक है, अब जब क्लास के तरीकों बनाम उदाहरण के तरीकों के बारे में भ्रम की स्थिति साफ हो गई है, तो मैं एक उत्तर दे सकता हूं :-)

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

C ++ और C # की चर्चा पहले ही दूसरों ने की है। जावा में, सबसे व्युत्पन्न प्रकार की आभासी विधि को बुलाया जाएगा, हालांकि उस प्रकार को अभी तक प्रारंभ नहीं किया गया है। इसलिए यदि वह विधि व्युत्पन्न प्रकार से किसी भी फ़ील्ड का उपयोग कर रही है, तो हो सकता है कि उस समय उन फ़ील्ड को ठीक से प्रारंभ न किया गया हो। इस समस्या पर Effecive Java 2nd Edition , Item 17: डिजाइन और दस्तावेज इनहेरिटेंस के लिए विस्तार से चर्चा की गई है या इसे प्रतिबंधित करें

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


3
(+1) "कंस्ट्रक्टर के अंदर रहते हुए, ऑब्जेक्ट अभी तक पूरी तरह से निर्मित नहीं है।" "वर्ग विधियों बनाम उदाहरण" के रूप में भी। कुछ प्रोग्रामिंग अपसंस्कृति इसे कंस्ट्रक्टर में प्रवेश करते समय निर्मित मानती है, जैसे कि प्रोग्रामर जहां कंस्ट्रक्टर को मान प्रदान करता है।
umlcat

7

मैं विधि कॉल को अपने आप में एक एंटीपैटर्न नहीं मानूंगा, एक कोड गंध। यदि कोई वर्ग एक resetविधि की आपूर्ति करता है, जो ऑब्जेक्ट को उसकी मूल स्थिति में लौटाता है, तो reset()कंस्ट्रक्टर में कॉल करना DRY है। (मैं रीसेट तरीकों के बारे में कोई बयान नहीं दे रहा हूं)।

यहां एक लेख है जो प्राधिकरण के लिए आपकी अपील को पूरा करने में मदद कर सकता है: http://misko.hevery.com/code-reviewers-guide/flaw-constructor-does-real-work/

यह वास्तव में कॉलिंग के तरीकों के बारे में नहीं है, लेकिन ऐसे निर्माणकर्ताओं के बारे में है जो बहुत अधिक करते हैं। IMHO, एक निर्माता में कॉलिंग विधियां एक गंध है जो यह संकेत दे सकती है कि एक निर्माता बहुत भारी है।

यह आपके कोड का परीक्षण करना कितना आसान है, इससे संबंधित है। कारणों में शामिल हैं:

  1. यूनिट परीक्षण में बहुत सारे निर्माण और विनाश शामिल हैं - इसलिए निर्माण तेजी से होना चाहिए।

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


3

दार्शनिक रूप से, निर्माता का उद्देश्य एक उदाहरण में स्मृति का एक कच्चा हिस्सा चालू करना है। जबकि कंस्ट्रक्टर को निष्पादित किया जा रहा है, वस्तु अभी तक मौजूद नहीं है, इसलिए इसके तरीकों को कॉल करना एक बुरा विचार है। आप नहीं जानते कि वे आंतरिक रूप से आखिर क्या करते हैं, और जब वे बुलाए जाते हैं तो वे वस्तु को कम से कम मौजूद (du!) मान सकते हैं।

तकनीकी रूप से, C ++ में और विशेष रूप से पायथन में, इसके साथ कुछ भी गलत नहीं हो सकता है, यह आपको सावधान रहना है।

व्यावहारिक रूप से, आपको केवल ऐसे तरीकों से कॉल को सीमित करना चाहिए जो वर्ग के सदस्यों को आरंभ करते हैं।


2

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

सैन ओओपी समर्थन वाली भाषाओं में, जो शुरू से ही व्यवहार्य सूचक को सही ढंग से सेट करता है, यह समस्या मौजूद नहीं है।


2

एक विधि को कॉल करने के साथ दो मुद्दे हैं:

  • एक आभासी पद्धति को कॉल करना, जो या तो कुछ अनपेक्षित (C ++) कर सकता है या उन वस्तुओं के हिस्सों का उपयोग कर सकता है जिन्हें अभी तक आरंभ नहीं किया गया है
  • सार्वजनिक विधि को कॉल करना (जिसे वर्ग के आक्रमणकारियों को लागू करना चाहिए), क्योंकि वस्तु आवश्यक रूप से अभी तक पूरी नहीं हुई है (और इस प्रकार इसका अपरिवर्तनीय धारक नहीं रह सकता है)

एक सहायक फ़ंक्शन को कॉल करने में कुछ भी गलत नहीं है, जब तक कि यह पिछले दो मामलों में नहीं आता है।


1

मैं इसे नहीं खरीदता। ऑब्जेक्ट-ओरिएंटेड सिस्टम में, एक विधि को कॉल करना बहुत ही एकमात्र चीज है जो आप कर सकते हैं। वास्तव में, यह कमोबेश "ऑब्जेक्ट ओरिएंटेड" की परिभाषा है। इसलिए, यदि कोई कंस्ट्रक्टर किसी भी तरीके से कॉल नहीं कर सकता है, तो वह क्या कर सकता है?


वस्तु को प्रारंभिक।
स्टेफानो बोरिनी

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

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

@ स्तेफ़ानो बोरीनी: "आप बिना किसी कॉल के राज्य को सीधे शुरू कर सकते हैं, सीधे अपनी वस्तु के आंतरिक हिस्से में।" अफसोस की बात है, जब इसमें एक विधि शामिल होती है, तो आप क्या करते हैं? कोड को कॉपी और पेस्ट करें?
एस.लॉट

1
@ S.Lott: नहीं, मैं इसे कॉल करता हूं, लेकिन मैं इसे ऑब्जेक्ट विधि के बजाय एक मॉड्यूल फ़ंक्शन रखने की कोशिश करता हूं, और यह रिटर्न डेटा प्रदान करता है जिसे मैं कंस्ट्रक्टर में ऑब्जेक्ट स्टेट में डाल सकता हूं। यदि मेरे पास वास्तव में एक वस्तु विधि है, तो मैं इसे निजी बनाऊंगा और स्पष्ट करूंगा कि यह प्रारंभिक नाम के लिए है, जैसे कि इसे एक उचित नाम देना। हालांकि, मैं निर्माणकर्ता से वस्तु स्थिति निर्धारित करने के लिए एक सार्वजनिक विधि कभी नहीं कहूंगा।
स्टेफानो बोरीनी

0

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

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

और अन्य तरीकों को न कहें जो अधिक जटिल चीजें करते हैं।

जैसा कि गुणों के लिए "गेटर्स" और "सेटलर्स" विधियां हैं, मैं आमतौर पर अपने राज्य को संग्रहीत करने के लिए निजी / संरक्षित चर का उपयोग करता हूं, साथ ही "गेटर्स" और "सेटलर्स" विधियों का उपयोग करता हूं।

कंस्ट्रक्टर में मैं "एक्सेसर्स" को कॉल किए बिना , गुण राज्य फ़ील्ड में "डिफ़ॉल्ट" मान असाइन करता हूं ।

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