क्या एक निर्माता जो अपने तर्कों को मान्य करता है वह SRP का उल्लंघन करता है?


66

मैं सिंगल रिस्पांसिबिलिटी प्रिंसिपल (एसआरपी) का यथासंभव पालन करने की कोशिश कर रहा हूं और प्रतिनिधियों पर भरोसा करने के लिए एक निश्चित पैटर्न (तरीकों पर एसआरपी के लिए) के लिए उपयोग किया जाता है। मैं जानना चाहूंगा कि क्या यह दृष्टिकोण ध्वनि है या यदि इसके साथ कोई गंभीर समस्या है।

उदाहरण के लिए, एक कंस्ट्रक्टर के लिए इनपुट की जांच करने के लिए, मैं निम्नलिखित विधि पेश कर सकता हूं ( Streamइनपुट यादृच्छिक है, कुछ भी हो सकता है)

private void CheckInput(Stream stream)
{
    if(stream == null)
    {
        throw new ArgumentNullException();
    }

    if(!stream.CanWrite)
    {
        throw new ArgumentException();
    }
}

यह विधि (यकीनन) एक से अधिक काम करती है

  • इनपुट्स की जाँच करें
  • अलग-अलग अपवादों को फेंक दो

एसआरपी का पालन करने के लिए मैंने इसलिए तर्क को बदल दिया

private void CheckInput(Stream stream, 
                        params (Predicate<Stream> predicate, Action action)[] inputCheckers)
{
    foreach(var inputChecker in inputCheckers)
    {
        if(inputChecker.predicate(stream))
        {
            inputChecker.action();
        }
    }
}

जो माना जाता है कि केवल एक ही चीज़ करता है (यह करता है?): इनपुट की जाँच करें। इनपुट्स की वास्तविक जाँच के लिए और अपवादों को फेंकने के तरीके जैसे मैंने पेश किए हैं

bool StreamIsNull(Stream s)
{
    return s == null;
}

bool StreamIsReadonly(Stream s)
{
    return !s.CanWrite;
}

void Throw<TException>() where TException : Exception, new()
{
    throw new TException();
}

और CheckInputपसंद कर सकते हैं

CheckInput(stream,
    (this.StreamIsNull, this.Throw<ArgumentNullException>),
    (this.StreamIsReadonly, this.Throw<ArgumentException>))

क्या यह पहले विकल्प से बिल्कुल भी बेहतर है, या क्या मैं अप्राकृतिक जटिलता का परिचय देता हूं? क्या कोई तरीका है जो मैं अभी भी इस पैटर्न को बेहतर बना सकता हूं, अगर यह बिल्कुल भी व्यवहार्य है?


26
मैं यह तर्क दे सकता हूं कि CheckInputअभी भी कई काम कर रहे हैं: यह दोनों एक सरणी पर पुनरावृत्ति कर रहा है और एक विधेय फ़ंक्शन को कॉल कर रहा है और एक क्रिया फ़ंक्शन को कॉल कर रहा है। क्या तब यह एसआरपी का उल्लंघन नहीं है?
बार्ट वैन इनगेन शानौ

8
हां, यही वह बिंदु है जिसे मैं बनाने की कोशिश कर रहा था।
बार्ट वैन इनगेन शानौ

135
यह याद रखना महत्वपूर्ण है कि यह एकल जिम्मेदारी सिद्धांत है; एकल क्रिया सिद्धांत नहीं। इसकी एक ज़िम्मेदारी है: सत्यापित करें कि स्ट्रीम परिभाषित है और लिखने योग्य है।
डेविड अर्नो

40
ध्यान रखें कि इन सॉफ्टवेयर सिद्धांतों का पूरा बिंदु कोड को अधिक पठनीय और रखरखाव योग्य बनाना है। आपके मूल CheckInput को पढ़ने और बनाए रखने वाले संस्करण की तुलना में बनाए रखना बहुत आसान है। वास्तव में, अगर मैं कभी भी आपके अंतिम CheckInput पद्धति में एक कोडबेस में आया था, तो मैं इसे सभी को स्क्रैप कर दूंगा और इसे फिर से लिखने के लिए मैच करूंगा कि आपके पास मूल रूप से क्या था।
१ 26 २६

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

जवाबों:


151

एसआरपी शायद सबसे गलत सॉफ्टवेयर सिद्धांत है।

एक सॉफ्टवेयर एप्लिकेशन मॉड्यूल से बनाया गया है, जो मॉड्यूल से बनाया गया है, जो ...

सबसे नीचे, एक एकल फ़ंक्शन जैसे कि CheckInputकेवल थोड़ा सा तर्क होगा, लेकिन जैसा कि आप ऊपर की ओर जाते हैं, प्रत्येक क्रमिक मॉड्यूल अधिक से अधिक तर्क समेटता है और यह सामान्य है

एसआरपी एक भी परमाणु कार्रवाई करने के बारे में नहीं है। यह एक ही जिम्मेदारी होने के बारे में है, भले ही उस जिम्मेदारी के लिए कई कार्यों की आवश्यकता हो ... और आखिरकार यह रखरखाव और परीक्षण क्षमता के बारे में है :

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

तथ्य यह CheckInputहै कि दो चेक के साथ लागू किया जाता है और दो अलग-अलग अपवादों को उठाना कुछ हद तक अप्रासंगिक है।

CheckInputएक संकीर्ण जिम्मेदारी है: यह सुनिश्चित करना कि इनपुट आवश्यकताओं का अनुपालन करता है। हां, कई आवश्यकताएं हैं, लेकिन इसका मतलब यह नहीं है कि कई जिम्मेदारियां हैं। हां, आप चेक को विभाजित कर सकते हैं, लेकिन यह कैसे मदद करेगा? कुछ बिंदु पर चेक को किसी तरह से सूचीबद्ध किया जाना चाहिए।

आइए तुलना करें:

Constructor(Stream stream) {
    CheckInput(stream);
    // ...
}

बनाम:

Constructor(Stream stream) {
    CheckInput(stream,
        (this.StreamIsNull, this.Throw<ArgumentNullException>),
        (this.StreamIsReadonly, this.Throw<ArgumentException>));
    // ...
}

अब, CheckInputकम करता है ... लेकिन इसका कॉलर अधिक करता है!

आपने आवश्यकताओं की सूची को CheckInputवहां से स्थानांतरित कर दिया है , जहां वे संलग्न हैं, Constructorजहां वे दिखाई दे रहे हैं।

क्या यह एक अच्छा बदलाव है? निर्भर करता है:

  • यदि CheckInputकेवल वहाँ कहा जाता है: यह बहस का मुद्दा है, एक तरफ यह आवश्यकताओं को दिखाई देता है, दूसरी तरफ यह कोड को बंद कर देता है;
  • यदि CheckInputएक ही आवश्यकताओं के साथ कई बार कहा जाता है , तो यह DRY का उल्लंघन करता है और आपके पास एक एनकैप्सुलेशन समस्या है।

यह महसूस करना महत्वपूर्ण है कि एक ही जिम्मेदारी बहुत काम कर सकती है । सेल्फ ड्राइविंग कार के "मस्तिष्क" में एक ही जिम्मेदारी होती है:

कार को अपने गंतव्य तक ले जाना।

यह एक जिम्मेदारी है, लेकिन सेंसर और अभिनेताओं की एक टन के समन्वय, निर्णय लेने की बहुत सारी की आवश्यकता है, और यहां तक कि संभवतः परस्पर विरोधी आवश्यकताओं है 1 ...

... हालाँकि, यह सब समझाया है। तो ग्राहक परवाह नहीं है।

1 यात्रियों की सुरक्षा, दूसरों की सुरक्षा, नियमों का सम्मान ...


2
मुझे लगता है कि जिस तरह से आप "एनकैप्सुलेशन" शब्द का उपयोग कर रहे हैं और इसका डेरिवेटिव भ्रमित कर रहा है। इसके अलावा, महान जवाब!
फाबियो तुरती

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

13
@SavaB: ज़रूर, लेकिन सिद्धांत एक ही है। एक मॉड्यूल की एक ही जिम्मेदारी होनी चाहिए, हालांकि उसके घटकों की तुलना में बड़ी गुंजाइश है।
मैथ्यू एम।

3
@ user949300 ठीक है, कैसे बस "ड्राइविंग के बारे में।" वास्तव में, "ड्राइविंग" जिम्मेदारी है और "सुरक्षित रूप से" और "कानूनी रूप से" इस जिम्मेदारी को पूरा करने के बारे में आवश्यकताएं हैं। और हम अक्सर एक जिम्मेदारी बताते हुए आवश्यकताओं को सूचीबद्ध करते हैं।
ब्रायन मैककचटन

1
"एसआरपी शायद सबसे गलत सॉफ्टवेयर सिद्धांत है।" जैसा कि इस उत्तर से पता चलता है :)
माइकल 8

41

SRP ( https://8thlight.com/blog/uncle-bob/2014/05/08/SingleReponsibilityPrinciple.html ) के बारे में अंकल बॉब का उद्धरण :

सिंगल रिस्पांसिबिलिटी प्रिंसिपल (एसआरपी) में कहा गया है कि प्रत्येक सॉफ्टवेयर मॉड्यूल में बदलाव का एक और एक ही कारण होना चाहिए।

... यह सिद्धांत लोगों के बारे में है।

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

... यही कारण है कि हम JSP में SQL नहीं डालते हैं। यही कारण है कि हम परिणामों की गणना करने वाले मॉड्यूल में HTML उत्पन्न नहीं करते हैं। यही कारण है कि व्यावसायिक नियमों को डेटाबेस स्कीमा का पता नहीं होना चाहिए। यही कारण है कि हम चिंताओं को अलग करते हैं।

वह बताते हैं कि सॉफ्टवेयर मॉड्यूल को विशिष्ट हितधारकों की चिंताओं का समाधान करना चाहिए। इसलिए, आपके प्रश्न का उत्तर देना:

क्या यह पहले विकल्प से बिल्कुल भी बेहतर है, या क्या मैं अप्राकृतिक जटिलता का परिचय देता हूं? क्या कोई तरीका है जो मैं अभी भी इस पैटर्न को बेहतर बना सकता हूं, अगर यह बिल्कुल भी व्यवहार्य है?

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


4
सबसे बढ़िया उत्तर। ओपी के बारे में विस्तार से बताने के लिए, यदि गार्ड चेक दो अलग-अलग हितधारकों / विभागों से आते हैं, तो checkInputs()विभाजित किया जाना चाहिए, checkMarketingInputs()और में कहना चाहिए checkRegulatoryInputs()। अन्यथा उन सभी को एक विधि में संयोजित करना ठीक है।
user949300

36

नहीं, यह परिवर्तन एसआरपी द्वारा सूचित नहीं किया गया है।

अपने आप से पूछें कि आपके चेकर में "वस्तु पारित एक धारा है" के लिए कोई जांच क्यों नहीं है । उत्तर स्पष्ट है: भाषा कॉलर को एक गैर-स्ट्रीम में गुजरने वाले प्रोग्राम को संकलित करने से रोकती है।

आपकी आवश्यकताओं को पूरा करने के लिए C # का प्रकार प्रणाली अपर्याप्त है; आपके चेक इनवियर्स के प्रवर्तन को लागू कर रहे हैं जिन्हें आज टाइप सिस्टम में व्यक्त नहीं किया जा सकता है । अगर यह कहने का कोई तरीका है कि यह विधि एक अशोभनीय लेखन योग्य धारा लेती है, तो आपने ऐसा लिखा होगा, लेकिन ऐसा नहीं है, इसलिए आपने अगला सबसे अच्छा काम किया: आपने रनटाइम पर टाइप प्रतिबंध लागू किया। उम्मीद है कि आपने इसे भी प्रलेखित किया है, ताकि डेवलपर्स जो आपके तरीके का उपयोग करते हैं, उन्हें इसका उल्लंघन न करना पड़े, उनके परीक्षण मामलों में असफल हो, और फिर समस्या को ठीक करें।

किसी विधि पर टाइप करना एकल जिम्मेदारी सिद्धांत का उल्लंघन नहीं है; न तो इसके पूर्ववर्तियों को लागू करने की विधि है और न ही इसके उत्तरदाताओं का आकलन करना।


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

23

सभी जिम्मेदारियां समान नहीं बनाई जाती हैं।

यहाँ छवि विवरण दर्ज करें

यहाँ छवि विवरण दर्ज करें

यहाँ दो दराज़ हैं। उन दोनों की एक जिम्मेदारी है। उनमें से प्रत्येक के नाम हैं जो आपको बताते हैं कि उनमें क्या है। एक चांदी के बर्तन है। दूसरा कबाड़ दराज है।

तो क्या अंतर है? चांदी के बर्तन बनाने वाला स्पष्ट करता है कि इसमें क्या नहीं है। जंक दराज हालांकि कुछ भी स्वीकार करता है जो फिट होगा। चांदी के बर्तन से चम्मच निकालना बहुत गलत लगता है। फिर भी मुझे लगता है कि कबाड़ दराज से हटा दिया जाता है कि याद किया जाएगा कुछ भी सोचने के लिए मुश्किल से दबाया जाता है। सच्चाई यह है कि आप दावा कर सकते हैं कि कुछ भी एक ही ज़िम्मेदारी है लेकिन आपको क्या लगता है कि अधिक ज़िम्मेदार एकल ज़िम्मेदारी है?

एक एकल जिम्मेदारी वाली वस्तु का मतलब यह नहीं है कि केवल एक चीज यहां हो सकती है। जिम्मेदारियां घोंसला बना सकती हैं। लेकिन उन घोंसले वाले जिम्मेदारियों को समझ में आना चाहिए, जब आप उन्हें यहां पाते हैं तो उन्हें आपको आश्चर्यचकित नहीं करना चाहिए और यदि वे चले गए थे तो आपको उन्हें याद करना चाहिए।

तो जब आप पेश करेंगे

CheckInput(Stream stream);

मैं अपने आप को चिंतित नहीं पाता कि यह दोनों इनपुट की जाँच कर रहा है और अपवादों को फेंक रहा है। मुझे चिंता होगी अगर यह इनपुट और बचत इनपुट दोनों की जाँच कर रहा हो। यह एक आश्चर्यजनक आश्चर्य है। एक तो मुझे याद नहीं होगा अगर यह चला गया था।


21

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

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

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

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


14

CheckInput भूमिका

सबसे पहले, मुझे स्पष्ट वहाँ बाहर डाल दिया, चलो CheckInput है भले ही वह विभिन्न पहलुओं जाँच कर रहा है एक बात कर रही है,। अंततः यह इनपुट की जाँच करता है । एक तर्क दे सकता है कि अगर आप बुलाए गए तरीकों से काम कर रहे हैं तो यह एक बात नहीं है DoSomething, लेकिन मुझे लगता है कि यह मानना ​​सुरक्षित है कि चेकिंग इनपुट बहुत अस्पष्ट नहीं है।

यदि आप चाहते हैं कि इनपुट को अपनी कक्षा में रखने के लिए तर्क की जांच नहीं करना चाहते, तो विधेयकों के लिए इस पैटर्न को जोड़ना उपयोगी हो सकता है, लेकिन आप जो हासिल करना चाहते हैं उसके लिए यह पैटर्न क्रियात्मक लगता है। यह केवल IStreamValidatorएक विधि के साथ एक अंतरफलक को पारित करने के लिए कहीं अधिक प्रत्यक्ष हो सकता है isValid(Stream)यदि वह है जो आप प्राप्त करना चाहते हैं। लागू करने वाला कोई भी वर्ग IStreamValidatorअपनी पसंद के अनुसार StreamIsNullया StreamIsReadonlyयदि वे चाहें तो इसका उपयोग कर सकते हैं , लेकिन केंद्रीय बिंदु पर वापस आते हुए, यह एकल जिम्मेदारी सिद्धांत को बनाए रखने के हितों में बना एक हास्यास्पद बदलाव है।

मानसिक स्वास्थ्य की जांच

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

निष्कर्ष

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


4

सिद्धांत जोरदार रूप से कोड का एक टुकड़ा नहीं बताता है "केवल एक ही काम करना चाहिए"।

एसआरपी में "जिम्मेदारी" को आवश्यकताओं के स्तर पर समझा जाना चाहिए। कोड की जिम्मेदारी व्यावसायिक आवश्यकताओं को पूरा करना है। SRP का उल्लंघन किया जाता है यदि कोई वस्तु एक से अधिक स्वतंत्र व्यावसायिक आवश्यकताओं को पूरा करती है । स्वतंत्र होने का अर्थ है कि एक आवश्यकता बदल सकती है जबकि दूसरी आवश्यकता यथावत रहती है।

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

SRP उल्लंघन का वास्तविक उदाहरण इस तरह कोड होगा:

var message = "Your package will arrive before " + DateTime.Now.AddDays(14);

यह कोड बहुत सरल है, लेकिन फिर भी यह अनुमान लगाने योग्य है कि पाठ अपेक्षित डिलीवरी की तारीख से स्वतंत्र रूप से बदल जाएगा, क्योंकि ये व्यवसाय के विभिन्न हिस्सों द्वारा तय किए जाते हैं।


वस्तुतः हर आवश्यकता के लिए एक अलग वर्ग एक अपवित्र दुःस्वप्न की तरह लगता है।
whatsisname

@whatsisname: तब शायद एसआरपी आपके लिए नहीं है। कोई भी डिजाइन सिद्धांत सभी प्रकार और परियोजनाओं के आकार के लिए लागू नहीं होता है। (लेकिन ध्यान रहे कि हम केवल स्वतंत्र आवश्यकताओं की बात कर रहे हैं (अर्थात स्वतंत्र रूप से बदल सकते हैं), न कि किसी भी आवश्यकता के बाद से यह सिर्फ इस बात पर निर्भर करेगा कि वे किस प्रकार से ठीक-ठीक निर्दिष्ट हैं।)
जैक्सबी

मुझे लगता है कि यह अधिक है कि एसआरपी को स्थितिजन्य निर्णय के एक तत्व की आवश्यकता होती है जो एक एकल आकर्षक वाक्यांश में वर्णन करने के लिए चुनौतीपूर्ण है।
whatsisname

@whatsisname: मैं पूरी तरह से सहमत हूं।
जैक्सबी


3

मुझे @ एरिकलिपर्ट के उत्तर से बिंदु पसंद है :

अपने आप से पूछें कि पास की गई वस्तु के लिए आपके चेकर में कोई चेक क्यों नहीं है । उत्तर स्पष्ट है: भाषा कॉलर को एक गैर-स्ट्रीम में गुजरने वाले प्रोग्राम को संकलित करने से रोकती है।

आपकी आवश्यकताओं को पूरा करने के लिए C # का प्रकार प्रणाली अपर्याप्त है; आपके चेक इनवियर्स के प्रवर्तन को लागू कर रहे हैं जिन्हें आज टाइप सिस्टम में व्यक्त नहीं किया जा सकता है । अगर यह कहने का कोई तरीका है कि यह विधि एक अशोभनीय लेखन योग्य धारा लेती है, तो आपने ऐसा लिखा होगा, लेकिन ऐसा नहीं है, इसलिए आपने अगला सबसे अच्छा काम किया: आपने रनटाइम पर टाइप प्रतिबंध लागू किया। उम्मीद है कि आपने इसे भी प्रलेखित किया है, ताकि डेवलपर्स जो आपके तरीके का उपयोग करते हैं, उन्हें इसका उल्लंघन न करना पड़े, उनके परीक्षण मामलों में असफल हो, और फिर समस्या को ठीक करें।

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

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

तो, फिर पता है कि कैसे सी # है Nullable<T>? चलिए इसे उल्टा करते हैं और बनाते हैं NonNullable<T>:

public struct NonNullable<T> where T : class
{
    public T Value { get; private set; }
    public NonNullable(T value)
    {
        if (value == null) { throw new NullArgumentException(); }
        this.Value = value;
    }
    //  Ease-of-use:
    public static implicit operator T(NonNullable<T> value) { return value.Value; }
    public static implicit operator NonNullable<T>(T value) { return new NonNullable<T>(value); }

    //  Hack-ish overloads that prevent null-literals from being implicitly converted into NonNullable<T>'s.
    public static implicit operator NonNullable<T>(Tuple<T> value) { return new NonNullable<T>(value.Item1); }
    public static implicit operator NonNullable<T>(Tuple<T, T> value) { return new NonNullable<T>(value.Item1); }
}

अब लिखने के बजाय

public void Foo(Stream stream)
{
  if (stream == null) { throw new NullArgumentException(); }

  // ...method code...
}

, बस लिखें:

public void Foo(NonNullable<Stream> stream)
{
  // ...method code...
}

फिर, तीन उपयोग-मामले हैं:

  1. Foo()एक गैर-अशक्त के साथ उपयोगकर्ता कॉल Stream:

    Stream stream = new Stream();
    Foo(stream);
    

    यह वांछित उपयोग-मामला है, और यह बिना-या-बिना काम करता है NonNullable<>

  2. Foo()एक अशक्त के साथ उपयोगकर्ता कॉल Stream:

    Stream stream = null;
    Foo(stream);
    

    यह एक कॉलिंग त्रुटि है। यहां NonNullable<>उपयोगकर्ता को सूचित करने में मदद मिलती है कि उन्हें ऐसा नहीं करना चाहिए, लेकिन यह वास्तव में उन्हें नहीं रोकता है। किसी भी तरह से, यह एक रन-टाइम में परिणाम है NullArgumentException

  3. इसके Foo()साथ उपयोगकर्ता कॉल null:

    Foo(null);

    nullरन-टाइम से पहलेNonNullable<> , उपयोगकर्ता को आईडीई में कोई त्रुटि नहीं होगी , इसलिए उपयोगकर्ता को एक त्रुटि मिलती है । यह नल-जाँच को टाइप सिस्टम में सौंप रहा है, जिस तरह SRP सलाह देगा।

आप अपने तर्कों के बारे में अन्य बातों का दावा करने के लिए इस पद्धति का विस्तार कर सकते हैं। उदाहरण के लिए, चूँकि आप एक लेखन योग्य स्ट्रीम चाहते हैं, इसलिए आप struct WriteableStream<T> where T:Streamउस चेक को nullऔर stream.CanWriteकंस्ट्रक्टर दोनों में परिभाषित कर सकते हैं । यह अभी भी एक रन-टाइम प्रकार की जाँच होगी, लेकिन:

  1. यह WriteableStreamकॉल करने वाले की आवश्यकता को इंगित करते हुए, क्वालिफायर के साथ प्रकार को सजाता है ।

  2. यह कोड में एक ही स्थान पर चेक करता है, इसलिए आपको चेक और throw InvalidArgumentExceptionप्रत्येक बार दोहराना नहीं है ।

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


3

वर्तमान में आपका दृष्टिकोण प्रक्रियात्मक है। आप Streamऑब्जेक्ट को तोड़ रहे हैं और इसे बाहर से मान्य कर रहे हैं। ऐसा मत करो - यह एनकैप्सुलेशन को तोड़ता है। Streamअपनी मान्यता के लिए जिम्मेदार होने दें । हम SRP को लागू करने की कोशिश नहीं कर सकते हैं जब तक कि हमें इसे लागू करने के लिए कुछ कक्षाएं नहीं मिली हैं।

यहाँ एक है Streamजो केवल एक कार्यवाही करता है अगर यह सत्यापन पास करता है:

class Stream
{
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }

        System.out.println("My action");
    }
}

लेकिन अब हम SRP का उल्लंघन कर रहे हैं! "एक वर्ग को बदलने का केवल एक कारण होना चाहिए।" हमें 1) सत्यापन और 2) वास्तविक तर्क मिला है। हमें दो कारण मिले हैं जिन्हें बदलने की आवश्यकता हो सकती है।

हम इसे डेकोरेट करने वाले डेकोरेटर्स के साथ हल कर सकते हैं । सबसे पहले, हमें अपने Streamइंटरफेस में बदलना होगा और इसे एक ठोस वर्ग के रूप में लागू करना होगा।

interface Stream
{
    void someAction();
}

class DefaultStream implements Stream
{
    @Override
    public void someAction()
    {
        System.out.println("My action");
    }
}

अब हम एक डेकोरेटर लिख सकते हैं जो एक लपेटता है Stream, Streamकार्रवाई के वास्तविक तर्क के लिए दिए गए सत्यापन और डिफर्स करता है।

class WritableStream implements Stream
{
    private final Stream stream;

    public WritableStream(final Stream stream)
    {
        this.stream = stream;
    }

    @Override
    public void someAction()
    {
        if(!stream.canWrite)
        {
            throw new ArgumentException();
        }
        stream.someAction();
    }
}

हम अब इनकी किसी भी तरह से रचना कर सकते हैं:

final Stream myStream = new WritableStream(
    new DefaultStream()
);

अतिरिक्त सत्यापन चाहते हैं? एक और डेकोरेटर जोड़ें।


1

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

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

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


0

जैसा कि अन्य उत्तरों में बताया गया है कि एसआरपी को अक्सर गलत समझा जाता है। यह परमाणु कोड होने के बारे में नहीं है जो केवल एक कार्य करता है। यह सुनिश्चित करने के बारे में है कि आपकी वस्तुएँ और विधियाँ केवल एक ही कार्य करती हैं, और यह कि केवल एक ही स्थान पर किया जाता है।

छद्म कोड में एक खराब उदाहरण देखें।

class Math
    private int a;
    private int b;
    def constructor(int x, int y) 
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

हमारे बल्कि बेतुके उदाहरण में, गणित के # निर्माता की "जिम्मेदारी" गणित की वस्तु को प्रयोग करने योग्य बनाना है। यह पहले इनपुट को सैनिटाइज़ करके ऐसा करता है, फिर यह सुनिश्चित करके कि मान -1 नहीं हैं।

यह मान्य SRP है क्योंकि निर्माता केवल एक ही काम कर रहा है। यह मैथ ऑब्जेक्ट तैयार कर रहा है। हालाँकि यह बहुत अधिक रखरखाव योग्य नहीं है। यह DRY का उल्लंघन करता है।

तो इस पर एक और पास ले चलो

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        cleanX(x)
        cleanY(y)
    end
    def cleanX(int x)
        if(x != null)
          a = x
        else if(x < 0)
          a = abs(x)
        else if (x == -1)
          throw "Some Silly Error"
        else
          a = 0
        end
   end
   def cleanY(int y)
        if(y != null)
           b = y
        else if(y < 0)
           b = abs(y)
        else if(y == -1)
           throw "Some Silly Error"
        else
         b = 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

इस पास में हम DRY के बारे में थोड़ा बेहतर हुए, लेकिन अभी भी हमारे पास DRY के साथ जाने के लिए एक रास्ता है। दूसरी ओर एसआरपी थोड़ा हटकर लगता है। अब हमारे पास एक ही काम के दो कार्य हैं। CleanX और CleanY दोनों इनपुट को साफ करते हैं।

चलो इसे एक और जाने दो

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i != null)
          return i
        else if(i < 0)
          return abs(i)
        else if (i == -1)
          throw "Some Silly Error"
        else
          return 0
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

अब अंततः DRY के बारे में बेहतर थे, और SRP समझौते में प्रतीत होता है। हमारे पास केवल एक जगह है जो "सैनिटाइज" काम करती है।

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

class Math
    private int a;
    private int b;
    def constructor(int x, int y)
        a = clean(x)
        b = clean(y)
    end
    def clean(int i)
        if(i == null)
          return 0
        else if (i == -1)
          throw "Some Silly Error"
        else
          return abs(i)
        end
    end
    def add()
        return a + b
    end
    def sub()
        return b - a
    end
end

अधिकांश वास्तविक दुनिया के मामलों में ऑब्जेक्ट अधिक जटिल होंगे और SRP को वस्तुओं के एक समूह में लागू किया जाएगा। उदाहरण के लिए आयु पिता, माता, पुत्र, पुत्री की हो सकती है, इसलिए जन्म की तारीख से आयु का पता लगाने वाली 4 कक्षाएं रखने के बजाय आपके पास एक व्यक्ति वर्ग होता है जो ऐसा करता है और 4 कक्षाएं उसी से प्राप्त होती हैं। लेकिन मुझे उम्मीद है कि यह उदाहरण समझाने में मदद करता है। SRP परमाणु क्रियाओं के बारे में नहीं है, बल्कि एक "काम" के बारे में है।


-3

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

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

यदि आप वास्तव में ऐसे अनुबंधों को लागू करना चाहते हैं, तो Debug.Assertअव्यवस्था को कम करने के लिए इसी तरह के उपयोग या कुछ पर विचार करें :

public AClassThatDefinitelyNeedsAWritableStream(Stream stream)
{
   Assert.That(stream.CanWrite, "Put crucial information here, and not inane bloat.");

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