व्यक्तिगत गुणों के बजाय पूरे वर्गों को मापदंडों के रूप में लेने के लिए एक वर्ग का डिज़ाइन


30

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

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

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

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

उपयोगकर्ता सिर्फ एक उदाहरण है। यह हमारे कोड में व्यापक रूप से प्रचलित है।

क्या मैं यह सोचने में सही हूं कि यह ओपन / बंद सिद्धांत का उल्लंघन है? न केवल मौजूदा कक्षाओं को बदलने का कार्य, बल्कि उन्हें पहले स्थान पर स्थापित करना ताकि भविष्य में व्यापक परिवर्तन की आवश्यकता हो?

यदि हम अभी Userऑब्जेक्ट में पास हुए हैं , तो मैं जिस वर्ग के साथ काम कर रहा हूं, उसमें थोड़ा बदलाव कर सकता हूं। अगर मुझे कोई पैरामीटर जोड़ना है, तो मुझे कक्षा के संदर्भ में दर्जनों बदलाव करने पड़ सकते हैं।

क्या इस प्रथा से कोई अन्य सिद्धांत टूट गए हैं? निर्भरता शायद? हालांकि हम एक अमूर्त का संदर्भ नहीं दे रहे हैं, केवल एक प्रकार का उपयोगकर्ता है, इसलिए उपयोगकर्ता इंटरफ़ेस के लिए कोई वास्तविक आवश्यकता नहीं है।

क्या अन्य, गैर-ठोस सिद्धांतों का उल्लंघन किया जा रहा है, जैसे कि बुनियादी रक्षात्मक प्रोग्रामिंग सिद्धांत?

क्या मेरे निर्माता को इस तरह दिखना चाहिए:

MyConstructor(GUID userid, String username)

या यह:

MyConstructor(User theUser)

इसे पोस्ट किया:

यह सुझाव दिया गया है कि इस प्रश्न का उत्तर "पास आईडी या वस्तु?" में दिया गया है। यह इस सवाल का जवाब नहीं देता है कि किसी भी तरह से जाने का निर्णय SOLID सिद्धांतों का पालन करने की कोशिश को प्रभावित करता है, जो इस सवाल के मूल में है।


11
@gnat: यह निश्चित रूप से डुप्लिकेट नहीं है। संभव डुप्लिकेट एक वस्तु पदानुक्रम में गहरी तक पहुंचने के लिए विधि जंजीर के बारे में है। यह सवाल उस बारे में बिल्कुल नहीं पूछता है।
ग्रेग बरगार्ड

2
दूसरे रूप का उपयोग अक्सर तब किया जाता है जब पारित किए जाने वाले मापदंडों की संख्या कम हो जाती है।
रॉबर्ट हार्वे

12
पहले हस्ताक्षर के बारे में मुझे जो बात पसंद नहीं है, वह यह है कि उपयोगकर्ता और उपयोगकर्ता नाम वास्तव में एक ही उपयोगकर्ता से उत्पन्न होते हैं, इसकी कोई गारंटी नहीं है। यह एक संभावित बग है जिसे हर जगह उपयोगकर्ता के आसपास से गुजरने से बचा जाता है। लेकिन निर्णय वास्तव में इस बात पर निर्भर करता है कि तर्कों के साथ तथाकथित तरीके क्या कर रहे हैं।
17 की 26

9
शब्द "पार्स" उस संदर्भ में कोई अर्थ नहीं रखता है जिसमें आप इसका उपयोग कर रहे हैं। क्या आपका मतलब इसके बजाय "पास" था?
कोनराड रुडोल्फ

5
के बारे में क्या Iमें SOLID? MyConstructorमूल रूप से अब कहते हैं "मुझे एक Guidऔर एक की आवश्यकता है string"। तो क्यों नहीं एक इंटरफ़ेस प्रदान कर रहा है एक Guidऔर एक string, Userउस इंटरफ़ेस को लागू करने और MyConstructorउस इंटरफ़ेस को लागू करने वाले उदाहरण पर निर्भर करते हैं? और यदि MyConstructorपरिवर्तन की आवश्यकता है , तो इंटरफ़ेस को बदलें। - इसने मुझे प्रदाता के बजाय उपभोक्ता से "संबंधित" होने के लिए इंटरफेस के बारे में सोचने में बहुत मदद की । इसलिए सोचें कि "एक उपभोक्ता के रूप में मुझे कुछ ऐसा चाहिए जो ऐसा करता है और" इसके बजाय "एक प्रदाता के रूप में मैं यह और वह कर सकता हूं"।
कोरक

जवाबों:


31

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

सरल डेटा प्रकारों को पास करना अच्छा है, जब तक कि उनका मतलब कुछ और न हो, जो वे हैं। इस उदाहरण पर विचार करें:

public class Foo
{
    public void Bar(int userId)
    {
        // ...
    }
}

और एक उदाहरण उपयोग:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user.Id);

क्या आप दोष को देख सकते हैं? संकलक नहीं कर सकता। पारित किया जा रहा "उपयोगकर्ता आईडी" सिर्फ एक पूर्णांक है। हम चर का नाम देते हैं, userलेकिन blogPostRepositoryऑब्जेक्ट से इसके मूल्य को इनिशियलाइज़ करते हैं , जो संभवतः BlogPostवस्तुओं को लौटाता है, वस्तुओं को नहीं User- फिर भी कोड संकलित करता है और आप एक winky runtime त्रुटि के साथ समाप्त होते हैं।

अब इस परिवर्तित उदाहरण पर विचार करें:

public class Foo
{
    public void Bar(User user)
    {
        // ...
    }
}

हो सकता है कि Barविधि केवल "उपयोगकर्ता आईडी" का उपयोग करती है लेकिन विधि हस्ताक्षर के लिए एक Userवस्तु की आवश्यकता होती है । अब पहले की तरह उसी उदाहरण के उपयोग पर वापस जाते हैं, लेकिन इसे पूरे "उपयोगकर्ता" में पास करने के लिए इसमें संशोधन करते हैं:

var user = blogPostRepository.Find(32);
var foo = new Foo();

foo.Bar(user);

अब हमारे पास कंपाइलर एरर है। blogPostRepository.Findविधि एक रिटर्न BlogPostवस्तु है, जो हम चतुराई से "उपयोगकर्ता" कहते हैं। फिर हम इस "उपयोगकर्ता" को Barविधि में पास करते हैं और तुरंत एक संकलक त्रुटि प्राप्त करते हैं, क्योंकि हम एक BlogPostऐसी विधि को पारित नहीं कर सकते हैं जो स्वीकार करता है User

भाषा के टाइप सिस्टम को सही कोड लिखने के लिए, और चलाने के समय के बजाय संकलन समय पर दोषों की पहचान करने के लिए उत्तोलन किया जा रहा है।

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


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

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

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

@ इयान: हालांकि ईमानदार होने के लिए, यहां तक ​​कि सी # में काम करते हुए Ids को लपेटने के लिए बहुत पसंद किया गया है और एक संरचना में सॉर्ट केवल थोड़ा और स्पष्टता देने के लिए।
ग्रेग बरगदट

1
"इसके स्थान पर एक पॉइंटर को पास करने में कुछ भी गलत नहीं है।" या एक संदर्भ, संकेत के साथ सभी मुद्दों से बचने के लिए जिसे आप चला सकते हैं।
Yay295

17

क्या मैं यह सोचने में सही हूं कि यह ओपन / बंद सिद्धांत का उल्लंघन है?

नहीं, यह उस सिद्धांत का उल्लंघन नहीं है। यह सिद्धांत उन Userतरीकों से संबंधित नहीं है जो कोड के अन्य भागों को प्रभावित करते हैं जो इसका उपयोग करते हैं। आपके परिवर्तन Userइस तरह के उल्लंघन हो सकते हैं, लेकिन यह असंबंधित है।

क्या इस प्रथा से कोई अन्य सिद्धांत टूट गए हैं? निर्भरता उलटा पेरैप्स?

नहीं। आप क्या वर्णन करते हैं - केवल उपयोगकर्ता विधि के आवश्यक भागों को प्रत्येक विधि में इंजेक्ट करना - विपरीत है: यह शुद्ध निर्भरता व्युत्क्रम है।

क्या अन्य, गैर-ठोस सिद्धांतों का उल्लंघन किया जा रहा है, जैसे कि बुनियादी रक्षात्मक प्रोग्रामिंग सिद्धांत?

नहीं। यह तरीका कोडिंग का एक सर्वमान्य तरीका है। यह ऐसे सिद्धांतों का उल्लंघन नहीं कर रहा है।

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

अपनी टिप्पणी को संबोधित करने के लिए:

अनावश्यक रूप से श्रृंखला के पांच स्तरों के नीचे एक नए मूल्य को पार्स करने के साथ समस्याएं हैं और फिर उन सभी मौजूदा तरीकों के सभी संदर्भों को बदल दें ...

इस मुद्दे का एक हिस्सा यह है कि आप स्पष्ट रूप से इस दृष्टिकोण को पसंद नहीं करते हैं, जैसा कि "अनावश्यक रूप से [पास] ..." टिप्पणी के अनुसार। और यह काफी उचित है; यहाँ कोई सही उत्तर नहीं है। यदि आपको यह बोझ लगता है तो इसे इस तरह न करें।

हालाँकि, खुले / बंद सिद्धांत के संबंध में, यदि आप उस का सख्ती से पालन करते हैं तो "... उन सभी मौजूदा तरीकों के सभी संदर्भों में से सभी को बदल दें ..." एक संकेत है कि उन तरीकों को संशोधित किया गया था, जब उन्हें होना चाहिए संशोधन के लिए बंद कर दिया। वास्तव में, हालांकि, खुले / बंद सिद्धांत सार्वजनिक एपीआई के लिए अच्छा समझ में आता है, लेकिन एक ऐप के आंतरिक के लिए बहुत मतलब नहीं है।

... लेकिन निश्चित रूप से उस सिद्धांत का पालन करने की योजना जहां तक ​​यह करना व्यावहारिक है, उसमें भविष्य के बदलाव की आवश्यकता को कम करने के लिए रणनीति शामिल होगी?

लेकिन फिर आप YAGNI क्षेत्र में घूमते हैं और यह अभी भी सिद्धांत के लिए रूढ़िवादी होगा। यदि आपके पास एक विधि है Fooजो एक उपयोगकर्ता नाम लेती है और फिर आप Fooजन्म की तारीख भी लेना चाहते हैं, तो सिद्धांत का पालन करते हुए, आप एक नई विधि जोड़ते हैं; Fooकुछ नहीं बदला है। सार्वजनिक APIs के लिए फिर से अच्छा अभ्यास है, लेकिन यह आंतरिक कोड के लिए एक बकवास है।

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


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

@ जिम्बो, मैंने अपनी टिप्पणी को आजमाने और संबोधित करने के लिए अपना उत्तर अपडेट कर दिया है।
डेविड अरनो

मैं आपके योगदान की सराहना करता हूं। Btw। यहां तक ​​कि रॉबर्ट सी मार्टिन ने स्वीकार नहीं किया है कि ओपन / क्लोज्ड सिद्धांत का एक कठिन नियम है। यह अंगूठे का एक नियम है जो अनिवार्य रूप से टूट जाएगा। सिद्धांत को लागू करना एक अभ्यास है जितना इसे लागू करने का प्रयास करना उतना ही व्यावहारिक है। यही कारण है कि मैंने पहले "व्यावहारिक" शब्द का उपयोग किया था।
जिम्बो

यह उपयोगकर्ता के बजाय उपयोगकर्ता के मापदंडों को पारित करने के लिए निर्भरता व्युत्क्रम नहीं है।
जेम्स एलिस-जोन्स

@ जेम्सएलीस-जोन्स, डिपेंडेंसी इन्वर्टिस निर्भरता को "पूछने" से, "बताने" के लिए फ़्लिप करता है। यदि आप एक Userउदाहरण में पास करते हैं और फिर उस ऑब्जेक्ट को एक पैरामीटर प्राप्त करने के लिए क्वेरी करते हैं, तो आप केवल आंशिक रूप से निर्भरता को कम कर रहे हैं; अभी भी कुछ पूछ रहा है। सच्ची निर्भरता व्युत्क्रम 100% है "बताओ, मत पूछो"। लेकिन यह एक जटिलता मूल्य पर आता है।
डेविड अर्नो

10

हां, किसी मौजूदा फ़ंक्शन को बदलना ओपन / बंद सिद्धांत का उल्लंघन है। आप कुछ ऐसा संशोधित कर रहे हैं जो आवश्यकताओं में परिवर्तन के कारण संशोधन के लिए बंद होना चाहिए। एक बेहतर डिजाइन (बदल नहीं जब आवश्यकताओं को बदलने के लिए) चीजें हैं जो करने के लिए उपयोगकर्ता में पारित करने के लिए किया जाएगा चाहिए उन पर काम करते हैं।

लेकिन यह इंटरफ़ेस अलगाव सिद्धांत के पीछे चल सकता है, क्योंकि आप जिस तरह से काम करने की जरूरत है उससे अधिक जानकारी के साथ गुजर सकता है ।

इसलिए, अधिकांश चीजों के साथ - यह निर्भर करता है

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

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


+1 लेकिन मुझे आपके वाक्यांश के बारे में निश्चित नहीं है "आप अधिक जानकारी के साथ गुजर सकते हैं"। जब आप पास होते हैं (उपयोगकर्ता theuser) तो आप सूचना के बहुत न्यूनतम पास करते हैं, एक वस्तु का संदर्भ। सही है कि संदर्भ का उपयोग अधिक जानकारी प्राप्त करने के लिए किया जा सकता है, लेकिन इसका मतलब है कि कॉलिंग कोड को इसे प्राप्त करने की आवश्यकता नहीं है। जब आप (GUID उपयोगकर्ता नाम, स्ट्रिंग उपयोगकर्ता नाम) पास करते हैं, तो कॉल की गई विधि हमेशा ऑब्जेक्ट के सार्वजनिक इंटरफ़ेस को खोजने के लिए User.find (उपयोगकर्ता नाम) को कॉल कर सकती है, इसलिए आप वास्तव में कुछ भी नहीं छिपाते हैं।
15

5
@dcorking, " जब आप पास होते हैं (उपयोगकर्ता theuser) तो आप सूचना के बहुत न्यूनतम पास करते हैं, एक वस्तु का संदर्भ "। आप उस ऑब्जेक्ट से संबंधित अधिकतम जानकारी पास करते हैं: संपूर्ण ऑब्जेक्ट। " इस विधि को हमेशा User.find (userid) ..." कह सकते हैं । एक अच्छी तरह से डिजाइन प्रणाली में, यह संभव नहीं होगा क्योंकि प्रश्न में विधि की पहुंच नहीं होगी User.find()। वास्तव में वहाँ भी नहीं करना चाहिए होना एक User.find। एक उपयोगकर्ता को खोजने की जिम्मेदारी कभी नहीं होनी चाहिए User
डेविड अरनो

2
@ डॉर्किंग - enh आप एक संदर्भ पारित कर रहे हैं जो छोटा होता है तकनीकी संयोग है। आप Userफ़ंक्शन को संपूर्ण युग्मित कर रहे हैं । शायद यह समझ में आता है। लेकिन हो सकता है कि फ़ंक्शन को केवल उपयोगकर्ता के नाम के बारे में ध्यान देना चाहिए - और उपयोगकर्ता की सम्मिलित तिथि जैसे सामान के साथ गुजरना, या पता अनुचित है।
तेलेस्टिन

@DavidArno शायद ओपी के स्पष्ट जवाब के लिए महत्वपूर्ण है। एक उपयोगकर्ता को किसकी जिम्मेदारी मिलनी चाहिए? क्या खोजक / कारखाने को कक्षा से अलग करने के डिजाइन सिद्धांत का कोई नाम है?
16

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

3

यह डिज़ाइन पैरामीटर ऑब्जेक्ट पैटर्न का अनुसरण करता है । यह उन समस्याओं को हल करता है जो विधि हस्ताक्षर में कई पैरामीटर होने से उत्पन्न होती हैं।

क्या मैं यह सोचने में सही हूं कि यह ओपन / बंद सिद्धांत का उल्लंघन है?

नहीं। इस पैटर्न को लागू करने से ओपन / क्लोज्ड सिद्धांत (OCP) सक्षम होता है। उदाहरण के लिए व्युत्पन्न वर्गों Userको पैरामीटर के रूप में प्रदान किया जा सकता है जो उपभोग वर्ग में एक अलग व्यवहार को प्रेरित करता है।

क्या इस प्रथा से कोई अन्य सिद्धांत टूट गए हैं?

यह हो सकता है। मुझे SOLID सिद्धांतों के आधार पर समझाइए।

एकल जिम्मेदारी सिद्धांत अगर यह डिजाइन के रूप में आप समझा दिया है (SRP) का उल्लंघन किया जा सकता है:

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

समस्या सभी जानकारी के साथ है । यदि Userवर्ग में कई गुण हैं, तो यह एक विशाल डेटा ट्रांसफर ऑब्जेक्ट बन जाता है, जो उपभोग करने वाले वर्गों के दृष्टिकोण से असंबंधित जानकारी को स्थानांतरित करता है। उदाहरण: उपभोग करने वाले वर्ग के दृष्टिकोण से UserAuthenticationसंपत्ति User.Idऔर User.Nameप्रासंगिक हैं, लेकिन नहीं User.Timezone

इंटरफ़ेस अलगाव सिद्धांत (आईएसपी) को भी एक समान तर्क के साथ का उल्लंघन किया है, लेकिन एक और परिप्रेक्ष्य कहते है। उदाहरण: मान लीजिए कि एक उपभोग करने वाले वर्ग UserManagementको संपत्ति User.Nameको विभाजित करने की आवश्यकता है User.LastNameऔर इसके User.FirstNameलिए वर्ग UserAuthenticationको भी संशोधित किया जाना चाहिए।

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

class User : IUserAuthenticationInfo, IUserLocationInfo { ... }

प्रत्येक इंटरफ़ेस को एक Userवर्ग को अपने ऑपरेशन को पूरा करने के लिए आवश्यक वर्ग के संबंधित गुणों का एक सबसेट उजागर करना चाहिए । गुणों के समूह देखें। इंटरफेस का पुन: उपयोग करने का प्रयास करें। इसके बजाय उपभोग करने वाले वर्ग के UserAuthenticationउपयोग के मामले IUserAuthenticationInfoमें User। फिर यदि संभव हो तो Userइंटरफेस को "स्टैंसिल" के रूप में उपयोग करके कई ठोस कक्षाओं में कक्षा को तोड़ दें ।


1
एक बार जब उपयोगकर्ता जटिल हो जाता है, तो उदाहरण के लिए संभव उप-केंद्रों का एक दहनशील विस्फोट होता है, यदि उपयोगकर्ता के पास सिर्फ 3 गुण हैं, तो 7 संभावित संयोजन हैं। आपका प्रस्ताव अच्छा लग रहा है लेकिन असाध्य है।
189 बजे user949300

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

2

जब मेरे अपने कोड में इस मुद्दे का सामना किया जाता है, तो मैंने निष्कर्ष निकाला है कि बुनियादी मॉडल कक्षाएं / वस्तुएं उत्तर हैं।

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

रिपॉजिटरी के लिए मेरे नियम-अंगूठे हैं:

  • जहां एक से अधिक विधि समान 2 या अधिक पैरामीटर लेती हैं, मापदंडों को एक मॉडल ऑब्जेक्ट के रूप में एक साथ समूहीकृत किया जाना चाहिए।

  • जहां एक विधि 2 से अधिक पैरामीटर लेती है, मापदंडों को एक मॉडल ऑब्जेक्ट के रूप में एक साथ समूहीकृत किया जाना चाहिए।

  • मॉडल एक सामान्य आधार से विरासत में मिल सकते हैं, लेकिन केवल तब जब यह वास्तव में समझ में आता है (आमतौर पर बाद में दिमाग में विरासत के साथ शुरुआत करने से बेहतर है)।


अन्य परतों / क्षेत्रों से मॉडल का उपयोग करने में समस्या तब तक स्पष्ट नहीं होती है जब तक कि परियोजना थोड़ा जटिल नहीं हो जाती है। यह तभी है जब आप पाते हैं कि कम कोड अधिक काम या अधिक जटिलताएं पैदा करता है।

और हाँ, अलग-अलग परतों / उद्देश्यों (यानी। ViewModels बनाम POCO) की सेवा करने वाले समान गुणों वाले 2 अलग-अलग मॉडल होना पूरी तरह से ठीक है।


2

आइए SOLID के व्यक्तिगत पहलुओं की जाँच करें:

  • एकल जिम्मेदारी: संभवत: यदि लोग वर्ग के केवल वर्गों के आसपास से गुजरते हैं, तो उनका उल्लंघन होता है।
  • खुला / बंद: अप्रासंगिक जहां कक्षा के वर्गों को पास किया जाता है, केवल जहां पूरी वस्तु को पास किया जाता है। (मुझे लगता है कि जहां संज्ञानात्मक असंगति में किक करता है: आपको फ़ारवे कोड को बदलने की आवश्यकता है लेकिन कक्षा स्वयं ठीक लगती है।)
  • Liskov प्रतिस्थापन: गैर-मुद्दा, हम उपवर्ग नहीं कर रहे हैं।
  • निर्भरता उलटा (सार पर निर्भर करती है, ठोस डेटा नहीं)। हाँ, इसका उल्लंघन किया गया है: लोगों के पास सार नहीं है, वे वर्ग के ठोस तत्वों को निकालते हैं और चारों ओर से गुजरते हैं। मुझे लगता है कि यहां मुख्य मुद्दा है।

एक चीज जो डिजाइन प्रवृत्ति को भ्रमित करती है, वह यह है कि वर्ग अनिवार्य रूप से वैश्विक वस्तुओं के लिए है, और अनिवार्य रूप से केवल पढ़ने के लिए है। ऐसी स्थिति में, अमूर्त का उल्लंघन करने से बहुत नुकसान नहीं होता है: बस पढ़ने वाले डेटा को संशोधित नहीं किया जाता है जो एक बहुत कमजोर युग्मन बनाता है; केवल जब यह एक बड़ा ढेर बन जाता है तो दर्द ध्यान देने योग्य हो जाता है।
डिज़ाइन वृत्ति को पुनः स्थापित करने के लिए, बस मान लें कि ऑब्जेक्ट बहुत वैश्विक नहीं है। क्या संदर्भ एक समारोह की जरूरत होगी अगरUser किसी वस्तु को कभी भी म्यूट किया जा सकता है, ? ऑब्जेक्ट के किन घटकों को एक साथ म्यूट किया जाएगा? इन्हें बाहर से विभाजित किया जा सकता है User, चाहे एक संदर्भित उप-विषय के रूप में या एक इंटरफ़ेस के रूप में जो संबंधित क्षेत्रों के "स्लाइस" को उजागर करता है, उतना महत्वपूर्ण नहीं है।

एक और सिद्धांत: उन कार्यों को देखें जो के कुछ हिस्सों का उपयोग करते हैं User देखें जो और देखते हैं कि कौन से फ़ील्ड (विशेषताएँ) एक साथ चलते हैं। यह उप-विषयों की एक अच्छी प्रारंभिक सूची है - आपको निश्चित रूप से सोचने की जरूरत है कि क्या वे वास्तव में एक साथ हैं।

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

विभाजित Userकरना वास्तव में बदसूरत हो जाएगा यदि उप-विषय ओवरलैप करते हैं, तो लोग भ्रमित होंगे कि कौन सा चुनना है यदि सभी आवश्यक फ़ील्ड ओवरलैप से हैं। यदि आप पदानुक्रमिक रूप से विभाजित होते हैं (उदाहरण के लिए आपके पास UserMarketSegmentजो, अन्य चीजों के अलावा, है UserLocation), तो लोग अनिश्चित होंगे कि जिस फ़ंक्शन को वे लिख रहे हैं वह किस स्तर पर है: क्या यह उपयोगकर्ता डेटा के Location स्तर या स्तर पर काम कर रहा हैMarketSegment है? यह वास्तव में मदद नहीं करता है कि यह समय के साथ बदल सकता है, यानी आप कभी-कभी एक पूरे कॉल चेन में फ़ंक्शन हस्ताक्षर बदल रहे हैं।

दूसरे शब्दों में: जब तक आप वास्तव में अपने डोमेन को नहीं जानते हैं और इस बारे में स्पष्ट विचार रखते हैं कि मॉड्यूल किन पहलुओं से निपट रहा है User, यह वास्तव में कार्यक्रम की संरचना में सुधार के लायक नहीं है।


1

यह वास्तव में एक दिलचस्प सवाल है। यह निर्भर करता है।

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

यदि आपको पूरा यकीन है कि आपको उपयोगकर्ता के ईमेल के अलावा किसी अन्य चीज़ का उपयोग करने की आवश्यकता नहीं होगी, तो आपको इसे पास करना चाहिए। इसका लाभ यह है कि आप तब व्यापक संदर्भों में विधि का उपयोग कर सकते हैं: आप इसे उदाहरण के लिए उपयोग कर सकते हैं किसी कंपनी के ईमेल के साथ या किसी ऐसे व्यक्ति के साथ जिसे किसी ने सिर्फ टाइप किया हो। इससे लचीलापन बढ़ता है।

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


1

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

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

interface IIdentifieable
{
    Guid ID { get; }
}

या

interface INameable
{
    string Name { get; }
}

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


1

यहाँ मैंने समय-समय पर कुछ देखा है:

  • एक विधि प्रकार User(या Productया जो भी) का एक तर्क लेती है जिसमें बहुत सारे गुण हैं, भले ही विधि केवल उनमें से कुछ का उपयोग करती है।
  • किसी कारण से, कोड के कुछ हिस्से को उस पद्धति को कॉल करने की आवश्यकता होती है, भले ही इसमें पूरी तरह से आबादी वाली Userवस्तु न हो। यह एक उदाहरण बनाता है और केवल उन गुणों को प्रारंभिक करता है, जिनकी विधि को वास्तव में आवश्यकता होती है।
  • यह कई बार होता है।
  • थोड़ी देर के बाद, जब आप एक विधि का सामना करते हैं जिसमें एक Userतर्क होता है, तो आप खुद को उस पद्धति पर कॉल करने के लिए ढूंढते हैं जहां से Userआता है ताकि आप जान सकें कि कौन से गुण आबाद हैं। क्या यह एक ईमेल पते के साथ "वास्तविक" उपयोगकर्ता है, या यह केवल एक उपयोगकर्ता आईडी और कुछ अनुमतियों को पारित करने के लिए बनाया गया था?

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

इससे भी बदतर, जब आपके पास एक उदाहरण हैUser , तो आपको यह जानना होगा कि यह कहाँ से आया है ताकि आप जान सकें कि कौन से गुण आबाद हैं। आप यह जानना नहीं चाहेंगे।

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

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

जहाँ संभव हो, अगले डेवलपर के लिए सही उदाहरण सेट करके केवल वही पास करें जो आपको पास करने की आवश्यकता है।

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