बिल्डर पैटर्न: कब असफल होना है?


45

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

पहले कुछ स्पष्टीकरण:

  • जल्दी असफल होने के साथ मेरा मतलब है कि एक अवैध पैरामीटर के अंदर जाते ही एक वस्तु का निर्माण विफल हो जाना चाहिए SomeObjectBuilder
  • देर से असफल होने के साथ मेरा मतलब है कि किसी ऑब्जेक्ट का निर्माण केवल उस build()कॉल पर विफल हो सकता है जो कि अनुमानित रूप से निर्मित ऑब्जेक्ट के एक निर्माता को कॉल करता है।

फिर कुछ तर्क:

  • देर से असफल होने के पक्ष में: एक बिल्डर वर्ग को उस वर्ग से अधिक नहीं होना चाहिए जो केवल मूल्य रखता है। इसके अलावा, यह कम कोड दोहराव की ओर जाता है।
  • जल्दी विफल होने के पक्ष में: सॉफ्टवेयर प्रोग्रामिंग में एक सामान्य दृष्टिकोण यह है कि आप जितनी जल्दी हो सके मुद्दों का पता लगाना चाहते हैं और इसलिए जांच करने के लिए सबसे तार्किक जगह बिल्डर वर्ग 'कंस्ट्रक्टर,' सेटलर्स 'और अंततः बिल्ड विधि में होगी।

इस बारे में आम सहमति क्या है?


8
मुझे देर से असफल होने का कोई फायदा नहीं दिखता। एक व्यक्ति जो कहता है कि एक बिल्डर वर्ग को "अच्छा डिजाइन" पर पूर्वता नहीं बरतनी चाहिए, और कीड़े को जल्दी पकड़ना हमेशा कीड़े को पकड़ने से बेहतर होता है।
डोभाल

3
इसे देखने का एक और तरीका यह है कि बिल्डर को यह नहीं पता होगा कि वैध डेटा क्या है। इस मामले में जल्दी असफल होना जैसे ही आपको पता चलता है कि इसमें कोई त्रुटि है। जल्दी असफल नहीं होने पर, nullकोई समस्या होने पर बिल्डर किसी वस्तु को वापस करेगा build()
क्रिस

यदि आप एक चेतावनी जारी करने का कोई तरीका नहीं जोड़ते हैं और प्रस्ताव को बिल्डर के भीतर ठीक करने का कोई मतलब नहीं है तो देर करने में कोई मतलब नहीं है।
मार्क

जवाबों:


34

आइए विकल्पों को देखें, जहां हम सत्यापन कोड डाल सकते हैं:

  1. बिल्डर में बसे अंदर।
  2. build()विधि के अंदर ।
  3. निर्मित इकाई के अंदर: यह build()विधि में लागू किया जाएगा जब इकाई बनाई जा रही है।

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

विकल्प 2 विकल्प 1 से काफी बदतर नहीं है, क्योंकि, आमतौर पर, बिल्डर में बसने से पहले build(), विशेष रूप से, धाराप्रवाह इंटरफेस में, आह्वान किया जाता है। इस प्रकार, अभी भी ज्यादातर मामलों में जल्दी समस्या का पता लगाना संभव है। हालाँकि, यदि बिल्डर ऑब्जेक्ट बनाने का एकमात्र तरीका नहीं है, तो यह सत्यापन कोड के दोहराव की ओर ले जाएगा, क्योंकि आपको हर जगह जहाँ आप एक ऑब्जेक्ट बनाते हैं, वहाँ इसकी आवश्यकता होगी। इस मामले में सबसे तार्किक समाधान संभव के रूप में बनाई गई वस्तु के करीब के रूप में सत्यापन करना होगा, अर्थात, इसके अंदर। और यह विकल्प 3 है

ठोस दृष्टिकोण से, बिल्डर में सत्यापन डालने से भी एसआरपी का उल्लंघन होता है: बिल्डर वर्ग के पास ऑब्जेक्ट बनाने के लिए डेटा एकत्र करने की जिम्मेदारी पहले से ही होती है। सत्यापन अपने स्वयं के आंतरिक स्थिति पर अनुबंध स्थापित कर रहा है, किसी अन्य वस्तु की स्थिति की जांच करना एक नई जिम्मेदारी है।

इस प्रकार, मेरे दृष्टिकोण से, न केवल डिजाइन के दृष्टिकोण से देर से विफल होना बेहतर है, बल्कि निर्माणकर्ता के अंदर ही नहीं बल्कि बिल्डर में भी असफल होना बेहतर है।

UPD: इस टिप्पणी ने मुझे एक और संभावना की याद दिला दी, जब बिल्डर के अंदर सत्यापन (विकल्प 1 या 2) समझ में आता है। इसका कोई मतलब नहीं है कि बिल्डर के पास अपने द्वारा बनाए गए ऑब्जेक्ट पर अपने अनुबंध हैं। उदाहरण के लिए, मान लें कि हमारे पास एक बिल्डर है जो विशिष्ट सामग्री के साथ एक स्ट्रिंग का निर्माण करता है, कहते हैं, संख्या सीमाओं की सूची 1-2,3-4,5-6। इस बिल्डर की तरह एक विधि हो सकती है addRange(int min, int max)। परिणामी स्ट्रिंग को इन नंबरों के बारे में कुछ भी नहीं पता है, न ही इसे जानना चाहिए। बिल्डर स्वयं स्ट्रिंग के प्रारूप को परिभाषित करता है और संख्याओं पर दबाव बनाता है। इस प्रकार, विधि addRange(int,int)को इनपुट संख्याओं को मान्य करना होगा और अधिकतम से कम न्यूनतम होने पर अपवाद को फेंकना होगा।

उस ने कहा, सामान्य नियम केवल बिल्डर द्वारा परिभाषित अनुबंधों को मान्य करना होगा।


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

अगर एक URI बिल्डर एक अपवाद फेंकता है अगर एक नल स्ट्रिंग पारित हो जाता है, तो क्या यह SOLID का उल्लंघन है? बकवास
Gusdor

@Gusdor हाँ, अगर यह खुद एक अपवाद फेंकता है। हालांकि, उपयोगकर्ता के दृष्टिकोण से, सभी विकल्प एक बिल्डर द्वारा फेंके गए अपवाद की तरह दिखते हैं।
इवान गामेल

तो क्यों नहीं एक वैध () है कि निर्माण () द्वारा कहा जाता है? इस तरह थोड़ा दोहराव, संगति और कोई एसआरपी उल्लंघन नहीं है। यह निर्माण के प्रयास के बिना डेटा को मान्य करना भी संभव बनाता है, और सत्यापन निर्माण के करीब है।
स्टेलरवोर्टेक्स

@StellarVortex इस मामले में दो बार मान्य किया जाएगा - एक बार बिल्डर में। (), और, यदि डेटा वैध है और हम उस कंस्ट्रक्टर में ऑब्जेक्ट के कंस्ट्रक्टर पर आगे बढ़ते हैं।
इवान गमेल

34

यह देखते हुए कि आप जावा का उपयोग करते हैं, लेख में जोशुआ बलोच द्वारा प्रदान किए गए आधिकारिक और विस्तृत मार्गदर्शन पर विचार करें और जावा ऑब्जेक्ट्स को बनाना और नष्ट करना (नीचे दिए गए उद्धरण में बोल्ड फ़ॉन्ट मेरा है):

एक निर्माता की तरह, एक बिल्डर अपने मापदंडों पर आक्रमणकारियों को लगा सकता है। निर्माण विधि इन आक्रमणकारियों की जांच कर सकती है। यह महत्वपूर्ण है कि उन्हें बिल्डर से ऑब्जेक्ट में पैरामीटर कॉपी करने के बाद चेक किया जाए, और उन्हें बिल्डर फ़ील्ड (आइटम 39) के बजाय ऑब्जेक्ट फ़ील्ड पर चेक किया जाए । यदि किसी भी आक्रमणकारी का उल्लंघन किया जाता है, तो निर्माण विधि को IllegalStateException(आइटम 60) फेंकना चाहिए । अपवाद की विस्तार विधि से संकेत मिलता है कि किस अपरिवर्तनीय का उल्लंघन किया गया है (आइटम 63)।

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

इस लेख पर संपादक के स्पष्टीकरण के अनुसार , उपरोक्त उद्धरण में "आइटम" प्रभावी जावा, द्वितीय संस्करण में प्रस्तुत नियमों का संदर्भ देते हैं ।

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

एक बिल्ड को लागू करने से पहले इन्वर्टर की जाँच करना गलत होगा, इसके लिए अधिक ठोस समझ के लिए, CarBuilder के एक लोकप्रिय उदाहरण पर विचार करें । बिल्डर के तरीकों को एक मनमाना क्रम में लागू किया जा सकता है और परिणामस्वरूप, किसी को वास्तव में पता नहीं चल सकता है कि निर्माण तक विशेष पैरामीटर मान्य है या नहीं।

गौर करें कि स्पोर्ट्स कार में 2 से अधिक सीटें नहीं हो सकती हैं, कोई कैसे जान सकता है कि setSeats(4)ठीक है या नहीं? यह केवल बिल्ड पर है जब कोई यह सुनिश्चित करने के लिए जान सकता है कि क्या setSportsCar()लागू किया गया था या नहीं, जिसका अर्थ है कि फेंकना है TooManySeatsExceptionया नहीं।


3
+1 क्या अपवाद प्रकारों को फेंकने की सिफारिश करने के लिए, वास्तव में मैं क्या देख रहा था।
जेंटिक्स

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

19

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

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

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

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


11

जहां तक ​​मुझे पता है, सामान्य अभ्यास (निश्चित नहीं है कि अगर आम सहमति है) तो जल्दी से जल्दी असफल होना है क्योंकि आप एक त्रुटि की खोज कर सकते हैं। इससे अनजाने में आपके API का दुरुपयोग करना और भी मुश्किल हो जाता है।

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

यदि आपके पास ऐसी स्थिति में होने का दुर्भाग्य है जहां एक विशेषता की वैधता दूसरों पर निर्भर करती है, तो आपके पास दो विकल्प हैं:

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

अधिकांश चीजों के साथ, यह एक संदर्भ में किया गया निर्णय है। यदि प्रसंग इसे जल्दी विफल करने के लिए अजीब या जटिल बनाता है, तो बाद के समय में चेक को स्थगित करने के लिए एक व्यापार-बंद किया जा सकता है, लेकिन विफल-फास्ट डिफ़ॉल्ट होना चाहिए।


इसलिए संक्षेप में, आप यह कह रहे हैं कि किसी वस्तु / आदिम प्रकार में कवर की जा सकने वाली हर चीज को जल्द से जल्द मान्य करना उचित है? जैसे unsigned, @NonNullइत्यादि
skivi

2
@skiwi बहुत, हाँ। डोमेन चेक, नल चेक, उस तरह की बात। मैं इसमें से बहुत कुछ डालने की वकालत नहीं करूंगा: बिल्डर आम तौर पर साधारण चीजें हैं।
JvR

1
यह ध्यान देने योग्य हो सकता है कि यदि एक पैरामीटर की वैधता दूसरे के मूल्य पर निर्भर करती है, तो कोई केवल एक पैरामीटर मान को अस्वीकार कर सकता है यदि कोई जानता है कि दूसरा "वास्तव में" स्थापित है । यदि कई बार [पूर्ववर्ती सेटिंग के साथ अंतिम सेटिंग के साथ] एक पैरामीटर मान सेट करने की अनुमति दी जाती है, तो कुछ मामलों में ऑब्जेक्ट सेट करने के लिए सबसे प्राकृतिक तरीका Xएक मान को पैरामीटर सेट करना हो सकता है जिसे अमान्य मान वर्तमान मूल्य दिया गया है Y, लेकिन एक मूल्य पर build()सेट Yकरने से पहले जो Xमान्य होगा ।
सुपरकैट

अगर कोई एक निर्माण कर रहा है Shapeऔर बिल्डर के पास WithLeftऔर WithRightगुण हैं, और कोई एक बिल्डर को एक अलग जगह पर एक वस्तु का निर्माण करने के लिए समायोजित करना चाहता है, तो यह आवश्यक है कि WithRightकिसी वस्तु को दाएं चलते समय सबसे पहले बुलाया जाए, और WithLeftजब इसे छोड़ा जाए, तो यह अनावश्यक जटिलता को जोड़ देगा। WithLeftपुराने किनारे के दाईं ओर बाएं किनारे को सेट करने की अनुमति देने की तुलना में, बशर्ते कि WithRightदाएं किनारे को ठीक करने से पहले buildकहा जाता है।
सुपरकैट

0

मूल नियम "जल्दी असफल" है।

थोड़ा और अधिक उन्नत नियम है "जितना जल्दी हो सके असफल"।

यदि कोई संपत्ति आंतरिक रूप से अमान्य है ...

CarBuilder.numberOfWheels( -1 ). ...  

... तो आप इसे तुरंत अस्वीकार कर दें।

अन्य मामलों को संयोजन में जांचे जाने के लिए मूल्यों की आवश्यकता हो सकती है और उन्हें बेहतर तरीके से निर्माण () विधि में रखा जा सकता है:

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