बिल्डर बनाम बिल्डर पैटर्न के टन के साथ कंस्ट्रक्टर


21

यह अच्छी तरह से जानते हैं कि यदि आपकी कक्षा में कई मापदंडों के साथ एक कंस्ट्रक्टर है, तो 4 से अधिक कहो, तो यह संभवतः एक कोड गंध है । यदि वर्ग एसआरपी को संतुष्ट करता है तो आपको पुनर्विचार करने की आवश्यकता है ।

लेकिन क्या होगा अगर हम निर्माण और ऑब्जेक्ट 10 या अधिक मापदंडों पर निर्भर करते हैं, और आखिरकार बिल्डर पैटर्न के माध्यम से उन सभी मापदंडों को स्थापित करने के साथ समाप्त हो जाते हैं? कल्पना कीजिए, आप Personअपनी व्यक्तिगत जानकारी, काम की जानकारी, दोस्तों की जानकारी, रुचियों की जानकारी, शिक्षा की जानकारी आदि के साथ एक प्रकार की वस्तु का निर्माण करते हैं। यह पहले से ही अच्छा है, लेकिन आप किसी भी तरह 4 से अधिक पैरामीटर सेट करते हैं, है ना? इस दो मामलों को एक ही क्यों नहीं माना जाता है?

जवाबों:


25

बिल्डर पैटर्न कई तर्कों की "समस्या" को हल नहीं करता है। लेकिन कई तर्क समस्याग्रस्त क्यों हैं?

  • वे संकेत देते हैं कि आपकी कक्षा बहुत अधिक काम कर रही है । हालांकि, कई प्रकार हैं जो वैध रूप से कई सदस्य होते हैं जिन्हें समझदारी से समूहीकृत नहीं किया जा सकता है।
  • कई आदानों के साथ एक फ़ंक्शन का परीक्षण और समझना घातीय रूप से अधिक जटिल हो जाता है - शाब्दिक रूप से!
  • जब भाषा नामित मापदंडों की पेशकश नहीं करती है, तो एक फ़ंक्शन कॉल स्व-दस्तावेजीकरण नहीं है । कई तर्कों के साथ एक फ़ंक्शन कॉल पढ़ना काफी मुश्किल है क्योंकि आपको पता नहीं है कि 7 वें पैरामीटर को क्या करना है। यदि 5 वीं और 6 वीं तर्क को गलती से स्वैप किया गया था, तो आप यह भी ध्यान नहीं देंगे, खासकर यदि आप एक गतिशील रूप से टाइप की गई भाषा में हैं या सब कुछ एक स्ट्रिंग होने के लिए होता है, या जब अंतिम पैरामीटर trueकिसी कारण से होता है।

पैरामीटर नाम का फ़ेकिंग

बिल्डर पैटर्न पतों इन समस्याओं, कई तर्क के साथ समारोह कॉल की अर्थात् रख-रखाव की चिंताओं में से केवल एक * । तो एक फंक्शन कॉल जैसे

MyClass o = new MyClass(a, b, c, d, e, f, g);

बना सकता है

MyClass o = MyClass.builder()
  .a(a).b(b).c(c).d(d).e(e).f(f).g(g)
  .build();

∗ बिल्डर पैटर्न मूल रूप से मिश्रित वस्तुओं को इकट्ठा करने के लिए एक प्रतिनिधित्व-अज्ञेयवादी दृष्टिकोण के रूप में अभिप्रेत था, जो मापदंडों के लिए सिर्फ नामित तर्कों की तुलना में कहीं अधिक आकांक्षा है। विशेष रूप से, बिल्डर पैटर्न को एक धाराप्रवाह इंटरफ़ेस की आवश्यकता नहीं होती है।

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

उन भाषाओं में जहां एक नए मूल्य प्रकार को परिभाषित करना आसान है, मैंने पाया है कि नामित तर्कों को अनुकरण करने के लिए माइक्रोटेपिंग / छोटे प्रकार का उपयोग करना बेहतर है । यह इसलिए नाम दिया गया है क्योंकि प्रकार वास्तव में छोटे हैं, लेकिन आप अंत में बहुत अधिक टाइपिंग करते हैं ;-)

MyClass o = new MyClass(
  new MyClass.A(a), new MyClass.B(b), new MyClass.C(c),
  new MyClass.D(d), new MyClass.E(e), new MyClass.F(f),
  new MyClass.G(g));

जाहिर है, प्रकार के नाम A, B, C, ... आत्म दस्तावेज़ीकृत कर ऐसे नाम हैं जो पैरामीटर, अक्सर एक ही नाम का अर्थ समझाना हो के रूप में आप पैरामीटर चर देना चाहते हैं चाहिए। बिल्डर-फॉर-नेम-दलील मुहावरे की तुलना में, आवश्यक कार्यान्वयन बहुत सरल है, और इस प्रकार कीड़े होने की संभावना कम है। उदाहरण के लिए (जावा-ईश सिंटैक्स के साथ):

class MyClass {
  ...
  public static class A {
    public final int value;
    public A(int a) { value = a; }
  }
  ...
}

संकलक आपको यह गारंटी देने में मदद करता है कि सभी तर्क प्रदान किए गए थे; एक बिल्डर के साथ आपको गुम तर्कों के लिए मैन्युअल रूप से जांचना होगा, या एक राज्य मशीन को होस्ट भाषा प्रकार सिस्टम में एन्कोड करना होगा - दोनों में संभवतः बग होंगे।

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

MyClass o = new MyClass(new MyClass.Arguments(){{ argA = a; argB = b; argC = c; ... }});

class MyClass {
  ...
  public static abstract class Arguments {
    public int argA;
    public String ArgB;
    ...
  }
}

हालांकि, खेतों को भूलना संभव है, और यह एक काफी भाषा-विशिष्ट समाधान है (मैंने जावास्क्रिप्ट, सी #, और सी में उपयोग किया है)।

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

इसलिए जब "कई अनाम मानदंड समस्या को बनाए रखने के लिए कोड को मुश्किल बनाते हैं " को संबोधित करने के लिए कई दृष्टिकोण हैं , तो अन्य समस्याएं बनी हुई हैं।

रूट समस्या का सामना करना

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

सबसे आसान और अक्सर सबसे अच्छा तरीका है, निर्माता के लिए पूरी तरह से निर्मित निर्भरता को पैरामीटर के रूप में लेना , हालांकि यह कॉलर को निर्भरता के प्रबंधन की जिम्मेदारी देता है - या तो आदर्श नहीं है, जब तक कि आपके डोमेन मॉडल में निर्भरता स्वतंत्र संस्थाएं नहीं हैं।

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

Dependencies deps = new Dependencies(a, d);
...
MyClass o = deps.newMyClass(b, c, e, f, g);

class MyClass {
  MyClass(Dependencies deps, B b, C c, E e, F f, G g) {
    this.depA = deps.newDepA(b, c);
    this.depB = deps.newDepB(e, f);
    this.g = g;
  }
  ...
}

class Dependencies {
  private A a;
  private D d;
  public Dependencies(A a, D d) { this.a = a; this.d = d; }
  public DepA newDepA(B b, C c) { return new DepA(a, b, c); }
  public DepB newDepB(E e, F f) { return new DepB(d, e, f); }
  public MyClass newMyClass(B b, C c, E e, F f, G g) {
    return new MyClass(deps, b, c, e, f, g);
  }
}

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


मैं सच में स्व-दस्तावेजीकरण के खिलाफ तर्क दूंगा। यदि आपके पास एक सभ्य IDE + सभ्य टिप्पणियां हैं, तो अधिकांश समय कंस्ट्रक्टर और पैरामीटर परिभाषा एक कीस्ट्रोके दूर है।
जेवियर एचई

एंड्रॉइड स्टूडियो 3.0 में, कंस्ट्रक्टर में पैरामीटर का नाम एक कंस्ट्रक्टर कॉल में पारित किए जा रहे मूल्य के निकट दिखाया गया है। उदाहरण: नया A (ऑपरेंड 1: 34, ऑपरेंड 2: 56); operand1 और operand2 कंस्ट्रक्टर में परम नाम हैं। कोड को अधिक पठनीय बनाने के लिए उन्हें IDE द्वारा दिखाया गया है। तो, पैरामीटर क्या है यह जानने के लिए परिभाषा में जाने की आवश्यकता नहीं है।
गार्नेट

9

बिल्डर पैटर्न आपके लिए कुछ भी हल नहीं करता है और डिजाइन विफलताओं को ठीक नहीं करता है।

यदि आपके पास निर्माण करने के लिए 10 मापदंडों की आवश्यकता वाला एक वर्ग है, तो इसे बनाने के लिए एक बिल्डर बनाना अचानक आपके डिजाइन को बेहतर नहीं बना देगा। आपको प्रश्न में कक्षा को फिर से तैयार करने का विकल्प चुनना चाहिए।

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


1

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

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

जब बहुत सारी जानकारी होती है, तो जानकारी को एक बार में एक टुकड़ा बनाना अपरिहार्य है। आप उन टुकड़ों को समूहिक रूप से समूहित कर सकते हैं - मानव अंतर्ज्ञान पर आधारित (जो प्रोग्रामर के लिए उपयोग करना आसान बनाता है), या उपयोग पैटर्न के आधार पर (सूचना के टुकड़े आमतौर पर उसी समय अपडेट किए जाते हैं)।

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

4 की गाइडलाइन सिर्फ एक नियम है। ऐसी परिस्थितियां हैं जिनके लिए 4 से अधिक तर्कों की आवश्यकता होती है और जिन्हें समूह में रखने के लिए कोई समझदार या तार्किक तरीका नहीं है। उन मामलों में, यह बनाने के लिए समझ में आ सकता है structजो सीधे या संपत्ति बसने वालों के साथ आबादी हो सकती है। ध्यान दें कि structऔर इस मामले में बिल्डर एक समान उद्देश्य पूरा करते हैं।

हालाँकि, आपके उदाहरण में, आपने पहले ही बता दिया है कि उन्हें समूह में लाने का तार्किक तरीका क्या होगा। यदि आप ऐसी स्थिति का सामना कर रहे हैं जहां यह सच नहीं है, तो शायद आप इसे एक अलग उदाहरण के साथ चित्रित कर सकते हैं।

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