SRP कहता है, बिना किसी अनिश्चितता के, कि एक वर्ग को कभी भी बदलने का एक कारण होना चाहिए।
प्रश्न में "रिपोर्ट" वर्ग का पुनर्निर्माण, इसकी तीन विधियाँ हैं:
printReport
getReportData
formatReport
Report
हर विधि में उपयोग किए जा रहे अनावश्यक को अनदेखा करना , यह देखना आसान है कि यह SRP का उल्लंघन क्यों करता है:
"प्रिंट" शब्द का अर्थ किसी प्रकार के UI, या वास्तविक प्रिंटर से है। इस वर्ग में यूआई या प्रस्तुति तर्क की कुछ मात्रा शामिल है। यूआई आवश्यकताओं में बदलाव से Report
वर्ग में बदलाव की आवश्यकता होगी ।
"डेटा" शब्द का अर्थ किसी प्रकार की डेटा संरचना से है, लेकिन वास्तव में क्या निर्दिष्ट नहीं करता है (XML? JSON? CSV)। बावजूद, अगर रिपोर्ट की "सामग्री" कभी बदलती है, तो यह विधि होगी। एक डेटाबेस या एक डोमेन के लिए युग्मन है।
formatReport
सामान्य रूप से एक विधि के लिए बस एक भयानक नाम है, लेकिन मैं इसे देखकर यह मानूंगा कि यह एक बार फिर यूआई के साथ कुछ करना है, और शायद यूआई का एक अलग पहलू है printReport
। तो, एक और, असंबंधित कारण बदलने के लिए।
तो यह एक वर्ग संभवतः एक डेटाबेस, एक स्क्रीन / प्रिंटर डिवाइस और लॉग या फ़ाइल आउटपुट या व्हाट्सएप के लिए कुछ आंतरिक स्वरूपण तर्क के साथ युग्मित है । एक कक्षा में तीनों कार्य करने से, आप निर्भरता की संख्या को गुणा कर रहे हैं और इस संभावना को तीन गुना कर रहे हैं कि कोई निर्भरता या आवश्यकता परिवर्तन इस वर्ग (या उस पर निर्भर कुछ और) को तोड़ देगा।
यहाँ समस्या का एक हिस्सा यह है कि आपने एक विशेष रूप से कांटेदार उदाहरण उठाया है। आपको शायद एक वर्ग नहीं बुलाया जाना चाहिए Report
, भले ही वह केवल एक ही काम करता हो , क्योंकि ... क्या रिपोर्ट? अलग-अलग डेटा और अलग-अलग आवश्यकताओं के आधार पर सभी "रिपोर्ट" पूरी तरह से अलग जानवर नहीं हैं? और एक रिपोर्ट कुछ ऐसा नहीं है जो पहले से ही स्वरूपित है, या तो स्क्रीन के लिए या प्रिंट के लिए?
लेकिन, उस अतीत को देखते हुए, और एक काल्पनिक ठोस नाम - चलो इसे कहते हैं IncomeStatement
(एक बहुत ही सामान्य रिपोर्ट) - एक उचित "SRPed" वास्तुकला में तीन प्रकार होंगे:
IncomeStatement
- डोमेन और / या मॉडल वर्ग जिसमें शामिल हैं और / या स्वरूपित रिपोर्ट पर दिखाई देने वाली जानकारी की गणना करता है ।
IncomeStatementPrinter
, जो शायद कुछ मानक इंटरफ़ेस को लागू करेगा IPrintable<T>
। एक प्रमुख विधि है, Print(IncomeStatement)
और शायद कुछ अन्य तरीके या गुण प्रिंट-विशिष्ट सेटिंग्स को कॉन्फ़िगर करने के लिए।
IncomeStatementRenderer
, जो स्क्रीन रेंडरिंग को हैंडल करता है और प्रिंटर क्लास से काफी मिलता-जुलता है।
आप अंततः IncomeStatementExporter
/ जैसे अधिक सुविधा-विशिष्ट वर्ग भी जोड़ सकते हैं IExportable<TReport, TFormat>
।
जेनेरिक और IoC कंटेनरों की शुरुआत के साथ आधुनिक भाषाओं में इसे काफी आसान बना दिया गया है। आपके अधिकांश एप्लिकेशन कोड को विशिष्ट IncomeStatementPrinter
वर्ग पर निर्भर होने की आवश्यकता नहीं है , यह किसी भी प्रकार की प्रिंट करने योग्य रिपोर्ट का उपयोग कर सकता है IPrintable<T>
और संचालित कर सकता है , जो आपको बेस क्लास के सभी कथित लाभों को एक विधि के साथ देता है और सामान्य एसआरपी उल्लंघनों में से कोई भी नहीं। । आईओसी कंटेनर पंजीकरण में वास्तविक कार्यान्वयन की आवश्यकता केवल एक बार घोषित की जानी चाहिए।Report
print
कुछ लोग, जब उपरोक्त डिज़ाइन के साथ सामना किया जाता है, तो कुछ इस तरह से जवाब देते हैं: "लेकिन यह प्रक्रियात्मक कोड जैसा दिखता है, और OOP का पूरा बिंदु हमें-प्राप्त करना था- डेटा और व्यवहार के पृथक्करण से!" जिसको मैं कहता हूं: गलत ।
IncomeStatement
है न सिर्फ "डाटा", और ऊपर उल्लिखित गलती क्या वे इस तरह के एक "पारदर्शी" वर्ग बनाने के द्वारा कुछ गलत कर रहे हैं और बाद में में असंबंधित कार्यक्षमता के सभी प्रकार जाम शुरू महसूस करने के लिए OOP लोगों का एक बहुत का कारण बनता है IncomeStatement
, (अच्छी तरह से है कि और सामान्य आलस्य)। यह वर्ग केवल डेटा के रूप में शुरू हो सकता है , लेकिन समय के साथ, गारंटी दी जाती है, यह एक मॉडल के रूप में समाप्त हो जाएगा ।
उदाहरण के लिए, एक वास्तविक आय विवरण में कुल राजस्व , कुल व्यय और शुद्ध आय लाइनें हैं। एक उचित रूप से डिज़ाइन की गई वित्तीय प्रणाली इन सबसे अधिक संभावना नहीं रखेगी क्योंकि वे लेनदेन डेटा नहीं हैं - वास्तव में, वे नए लेनदेन डेटा के अतिरिक्त के आधार पर बदलते हैं। हालाँकि, इन पंक्तियों की गणना हमेशा एक ही होने वाली है, चाहे आप रिपोर्ट को प्रिंट, रेंडर कर रहे हों या निर्यात कर रहे हों। तो अपने IncomeStatement
वर्ग के रूप में इसे करने के लिए व्यवहार भी पर्याप्त मात्रा में है करने के लिए जा रहा है getTotalRevenues()
, getTotalExpenses()
और getNetIncome()
विधियों, और शायद कई अन्य। यह अपने व्यवहार के साथ एक वास्तविक ओओपी-शैली की वस्तु है, भले ही यह वास्तव में "बहुत" करने के लिए प्रतीत न हो।
लेकिन format
और print
तरीकों, वे जानकारी के साथ ही कुछ नहीं करना है। वास्तव में, यह बहुत कम संभावना नहीं है कि आप इन विधियों के कई कार्यान्वयन करना चाहते हैं , उदाहरण के लिए प्रबंधन के लिए एक विस्तृत विवरण और शेयरधारकों के लिए एक नहीं-तो-विस्तृत विवरण। इन स्वतंत्र कार्यों को अलग-अलग वर्गों में अलग-अलग करने से आपको एक-आकार-फिट print(bool includeDetails, bool includeSubtotals, bool includeTotals, int columnWidth, CompanyLetterhead letterhead, ...)
विधि के बोझ के बिना रनटाइम पर अलग-अलग कार्यान्वयन का चयन करने की क्षमता मिलती है । नीरस
उम्मीद है कि आप देख सकते हैं कि ऊपर, बड़े पैमाने पर पैरामीटर वाली विधि गलत हो गई है, और जहां अलग-अलग कार्यान्वयन सही हैं; एकल-वस्तु मामले में, हर बार जब आप मुद्रण तर्क में एक नई शिकन जोड़ते हैं, तो आपको अपना डोमेन मॉडल बदलना होगा ( टिम इन फाइनेंस पेज नंबर चाहता है, लेकिन केवल आंतरिक रिपोर्ट में, क्या आप इसे जोड़ सकते हैं? ) विरोध के रूप में ? इसके बजाय एक या दो उपग्रह कक्षाओं के लिए एक विन्यास संपत्ति जोड़ना।
SRP को ठीक से लागू करना निर्भरता के प्रबंधन के बारे में है । संक्षेप में, यदि एक वर्ग पहले से ही कुछ उपयोगी करता है, और आप एक और विधि जोड़ने पर विचार कर रहे हैं जो एक नई निर्भरता (जैसे कि यूआई, एक प्रिंटर, एक नेटवर्क, एक फ़ाइल, जो भी हो) को शुरू करेगा, नहीं । इस बारे में सोचें कि आप इसके बजाय एक नई कक्षा में इस कार्यक्षमता को कैसे जोड़ सकते हैं , और आप इस नई कक्षा को अपने समग्र आर्किटेक्चर में कैसे फिट कर सकते हैं (यह बहुत आसान है जब आप निर्भरता इंजेक्शन के आसपास डिजाइन करते हैं)। वह सामान्य सिद्धांत / प्रक्रिया है।
साइड नोट: रॉबर्ट की तरह, मैं इस धारणा को खारिज करता हूं कि एसआरपी-अनुपालन वर्ग में केवल एक या दो राज्य चर होने चाहिए। इस तरह के एक पतले रैपर को शायद ही कभी उपयोगी होने की उम्मीद की जा सकती है। तो इस के साथ जहाज पर मत जाओ।