क्लीन कोड और हाइब्रिड ऑब्जेक्ट और फ़ीचर ईर्ष्या


10

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

संकर

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

इस तरह के संकर नए कार्यों को जोड़ना कठिन बनाते हैं, लेकिन नए डेटा संरचनाओं को जोड़ना भी कठिन बनाते हैं। वे दोनों दुनिया के सबसे बुरे हैं। उन्हें बनाने से बचें। वे एक गुथे हुए डिज़ाइन के संकेत हैं जिनके लेखक अनिश्चित हैं - या इससे भी बदतर, अज्ञानी हैं - चाहे उन्हें कार्यों या प्रकारों से सुरक्षा की आवश्यकता हो।

हाल ही में मैं अपने एक कार्यकर्ता ऑब्जेक्ट के कोड को देख रहा था (जो विज़िटर पैटर्न को लागू करने के लिए होता है ) और इसे देखा।

@Override
public void visit(MarketTrade trade) {
    this.data.handleTrade(trade);
    updateRun(trade);
}

private void updateRun(MarketTrade newTrade) {
    if(this.data.getLastAggressor() != newTrade.getAggressor()) {
        this.data.setRunLength(0);
        this.data.setLastAggressor(newTrade.getAggressor());
    }
    this.data.setRunLength(this.data.getRunLength() + newTrade.getLots());
}

मैंने तुरंत अपने आप से कहा "फीचर ईर्ष्या! यह तर्क Dataकक्षा में होना चाहिए - विशेष रूप से handleTradeविधि में। handleTradeऔर हमेशा एक साथ updateRunहोना चाहिए "। लेकिन फिर मैंने सोचा "डेटा क्लास सिर्फ एक डेटा संरचना है, अगर मैं ऐसा करना शुरू कर दूंगा, तो यह हाइब्रिड ऑब्जेक्ट आएगा!"public

क्या बेहतर है और क्यों? आप कैसे तय करते हैं कि क्या करना है?


2
All डेटा ’को डेटा संरचना क्यों होना चाहिए? इसका स्पष्ट व्यवहार है। तो बस सभी गेटर्स को बंद करें और बस जाएं, ताकि कोई भी वस्तु आंतरिक स्थिति में हेरफेर न कर सके।
कॉर्मैक मुलहाल

जवाबों:


9

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

उदाहरण के लिए, हम तर्क दे सकते हैं कि एक 3D वेक्टर एक गूंगा रिकॉर्ड है। हालांकि, हमें इस तरह की विधि को जोड़ने से नहीं रोकना चाहिए add, जिससे वैक्टर को जोड़ना आसान हो जाता है। व्यवहार को जोड़ने से हाइब्रिड में रिकॉर्ड नहीं (इतना गूंगा नहीं) होता है।

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

  • किसी व्यापार को संभालने के बाद, अंतिम हमलावर को अद्यतन नहीं किया जा सकता है।
  • कोई नया व्यापार न होने पर भी अंतिम आक्रमणकारी को अद्यतन किया जा सकता है।
  • जब हमलावर को अपडेट किया गया था तब भी रन की लंबाई उसके पुराने मूल्य को बनाए रख सकती है।
  • आदि।

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

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

मैंने हाल ही में एन्कैप्सुलेशन पर एक उत्तर लिखा है , जो इस बात पर अधिक गहराई में जाता है कि एनकैप्सुलेशन क्या है और आप इसे कैसे सुनिश्चित कर सकते हैं।


5

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

आमतौर पर रिफ्लेक्टर करने के लिए सही तरीका यह है कि निर्भरता बाहर प्रत्येक विधि के लिए एक परिणाम है जिसे या तो अगली विधि में खिलाया जा सकता है या सीधे कार्य किया जा सकता है।

पुराना कोड:

MyObject x = ...;
x.actionOne();
x.actionTwo();
String result = x.actionThree();

नया कोड:

MyObject x = ...;
OneResult r1 = x.actionOne();
TwoResult r2 = r1.actionTwo();
String result = r2.actionThree();

इसके कई फायदे हैं:

  • यह अलग-अलग वस्तुओं ( SRP ) में अलग-अलग चिंताओं को स्थानांतरित करता है ।
  • यह अस्थायी युग्मन को हटा देता है: विधियों को आदेश से बाहर करना असंभव है, और विधि हस्ताक्षर उन्हें कैसे कॉल करने के बारे में अंतर्निहित दस्तावेज प्रदान करते हैं। क्या आपने कभी दस्तावेज़ीकरण को देखा है, जिस वस्तु को आप चाहते हैं उसे देखा है, और पीछे की ओर काम किया है? मुझे ऑब्जेक्ट Z चाहिए। लेकिन मुझे Z पाने के लिए Y की आवश्यकता है। Y पाने के लिए, मुझे X की आवश्यकता है। Aha! मेरे पास एक डब्ल्यू है, जिसे एक एक्स को प्राप्त करना आवश्यक है। यह सब एक साथ चेन करते हैं, और आपका डब्ल्यू अब जेड प्राप्त करने के लिए इस्तेमाल किया जा सकता है।
  • इस तरह की वस्तुओं को विभाजित करना उन्हें अपरिवर्तनीय बनाने की अधिक संभावना है, जो इस प्रश्न के दायरे से परे कई फायदे हैं। त्वरित रास्ता यह है कि अपरिवर्तनीय वस्तुएं उस कोड का नेतृत्व करती हैं जो अधिक सुरक्षित है।

उन दो विधि कॉलों के बीच कोई अस्थायी युग्मन नहीं है। उनके आदेश की अदला-बदली करें और व्यवहार न बदले।
डुर्रोन 597

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

की दृश्यता updateRunअप्रासंगिक है, जो महत्वपूर्ण है उसका क्रियान्वयन this.dataप्रश्न में मौजूद नहीं है और आगंतुक वस्तु द्वारा हेरफेर की जा रही वस्तु है।

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

0

मेरे दृष्टिकोण से एक वर्ग में "राज्य के लिए मूल्य (सदस्य चर) और व्यवहार के कार्यान्वयन (सदस्य कार्य, विधियाँ)" होने चाहिए।

यदि आप क्लास स्टेट मेंबर-वेरिएबल्स (या उनके गेटर्स / सेटर) को सार्वजनिक करते हैं, तो "दुर्भाग्यपूर्ण हाइब्रिड डेटा स्ट्रक्चर्स" उभरता है, जो सार्वजनिक नहीं होना चाहिए।

इसलिए मुझे डेटा डेटा ऑब्जेक्ट्स और वर्कर ऑब्जेक्ट्स के लिए अलग कक्षाएं लगाने की आवश्यकता नहीं है।

आपको राज्य-सदस्य-चर को गैर-सार्वजनिक रखने में सक्षम होना चाहिए (आपकी डेटाबेस-परत गैर-सार्वजनिक सदस्य-चर को संभालने में सक्षम होनी चाहिए)

फीचर ईर्ष्या एक ऐसा वर्ग है जो किसी अन्य वर्ग के तरीकों का अत्यधिक उपयोग करता है। Code_smell देखें । तरीकों और राज्य के साथ एक वर्ग होने से यह समाप्त हो जाएगा।

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