क्या किसी वस्तु को संदर्भ द्वारा पारित करना एक बुरा अभ्यास है?


12

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

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

पुरानी विधि:

public void InsertUser(User user) {
    user.Username = GenerateUsername(user);
    user.Password = GeneratePassword(user);

    context.Users.Add(user);
}

नई विधियाँ:

public void InsertUser(User user) {
    SetUsername(user);
    SetPassword(user);

    context.Users.Add(user);
}

private void SetUsername(User user) {
    var username = "random business logic";

    user.Username = username;
}

private void SetPassword(User user) {
    var password = "more business logic";

    user.Password = password;
}

मूल रूप से, किसी अन्य विधि से एक संपत्ति के मूल्य को खराब अभ्यास करने का अभ्यास है?


6
बस इतना कहा गया है ... आप यहाँ संदर्भ द्वारा कुछ भी पारित नहीं किया है। मूल्य द्वारा संदर्भ पास करना सभी एक ही बात पर नहीं है।
cHao

4
@ कोको: यह एक अंतर के बिना एक अंतर है। कोड का व्यवहार समान रूप से समान है।
रॉबर्ट हार्वे

2
@JDDavis: मौलिक रूप से, आपके दो उदाहरणों के बीच एकमात्र वास्तविक अंतर यह है कि आपने सेट उपयोगकर्ता नाम और सेट पासवर्ड क्रियाओं को एक सार्थक नाम दिया है।
रॉबर्ट हार्वे

1
आपके सभी कॉल यहां संदर्भ द्वारा हैं। मान द्वारा पास करने के लिए आपको C # में एक मान प्रकार का उपयोग करना होगा।
फ्रैंक हिलमैन

2
@FrankHileman: यहां सभी कॉल वैल्यू के हिसाब से हैं। वह C # में डिफ़ॉल्ट है। पासिंग एक संदर्भ और गुजर द्वारा संदर्भ अलग जानवरों रहे हैं, और भेद फर्क पड़ता है। यदि userसंदर्भ द्वारा पारित किया गया था, तो कोड इसे कॉलर के हाथों से बाहर निकाल सकता है और इसे केवल कहने से बदल सकता है, कह सकते हैं user = null;
cHao

जवाबों:


10

यहाँ मुद्दा यह है कि Userवास्तव में दो अलग-अलग चीजें हो सकती हैं:

  1. एक पूर्ण उपयोगकर्ता इकाई, जिसे आपके डेटा स्टोर में भेजा जा सकता है।

  2. उपयोगकर्ता इकाई बनाने की प्रक्रिया शुरू करने के लिए कॉलर से आवश्यक डेटा तत्वों का सेट। सिस्टम को उपयोगकर्ता नाम और पासवर्ड जोड़ना होगा, क्योंकि यह वास्तव में # 1 ऊपर के रूप में मान्य उपयोगकर्ता है।

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

मेरा सुझाव है कि आपको दो संस्थाओं की आवश्यकता होगी, जैसे एक Userवर्ग और एक EnrollRequestवर्ग। बाद वाले में वह सब कुछ हो सकता है जो आपको उपयोगकर्ता बनाने के लिए जानना चाहिए। यह आपके उपयोगकर्ता वर्ग की तरह दिखेगा, लेकिन उपयोगकर्ता के नाम और पासवर्ड के बिना। तब आप ऐसा कर सकते हैं:

public User InsertUser(EnrollRequest request) {
    var userName = GenerateUserName();
    var password = GeneratePassword();

    //You might want to replace this with a factory call, but "new" works here as an example
    var newUser = new User
    (
        request.Name, 
        request.Email, 
        userName, 
        password
    );
    context.Users.Add(user);
    return newUser;
}

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


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

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

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

5
@CandiedOrange मैं सुझाव देता हूं कि आप व्यवसाय के वैचारिक, सादे-अंग्रेजी संस्थाओं के साथ कार्यान्वयन की ठीक-ठीक परिभाषित प्रकार प्रणाली को कसने की कोशिश नहीं करेंगे। "उपयोगकर्ता" की एक व्यावसायिक अवधारणा निश्चित रूप से दो वर्गों में लागू की जा सकती है, यदि अधिक नहीं, कार्यात्मक रूप से महत्वपूर्ण वेरिएंट का प्रतिनिधित्व करने के लिए। एक उपयोगकर्ता जो निरंतर नहीं है, उसे एक विशिष्ट उपयोगकर्ता नाम की गारंटी नहीं है, जिसके पास कोई भी प्राथमिक कुंजी नहीं है जिससे लेनदेन को जोड़ा जा सकता है, और यह भी हस्ताक्षर नहीं कर सकता है, इसलिए मैं कहूंगा कि इसमें एक महत्वपूर्ण संस्करण शामिल है। एक आम आदमी के लिए, निश्चित रूप से, EnrollRequest और उपयोगकर्ता दोनों अपनी अस्पष्ट भाषा में "उपयोगकर्ता" हैं।
जॉन वू

5

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

 repo.InsertUser(user);

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

इसे हल करने के लिए, आप या तो कर सकते हैं

  • डालने से अलग इनिशियलाइज़ेशन (ताकि InsertUserपूरी तरह से इनिशियलाइज्ड Userऑब्जेक्ट प्रदान करने के लिए जरूरतों का कॉलर , या,

  • उपयोगकर्ता ऑब्जेक्ट की निर्माण प्रक्रिया में आरंभीकरण का निर्माण करें (जैसा कि कुछ अन्य उत्तरों द्वारा सुझाया गया है), या

  • बस उस विधि के लिए एक बेहतर नाम खोजने की कोशिश करें जो अधिक स्पष्ट रूप से व्यक्त करता है कि यह क्या करता है।

तो जैसे एक विधि नाम का चयन PrepareAndInsertUser, OrchestrateUserInsertion, InsertUserWithNewNameAndPasswordया जो भी आप पक्ष प्रभाव अधिक स्पष्ट करने के पसंद करते हैं।

बेशक, इस तरह के एक लंबे विधि नाम से संकेत मिलता है कि विधि शायद "बहुत अधिक" (एसआरपी उल्लंघन) कर रही है, लेकिन कभी-कभी आप यह नहीं चाहते हैं या आसानी से इसे ठीक नहीं कर सकते हैं, और यह कम से कम घुसपैठ, व्यावहारिक समाधान है।


3

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

किसी भी स्थिति में आप user.UserNameऔर सेट कर रहे हैं user.Password। मेरे पास पासवर्ड आइटम के बारे में आरक्षण है, लेकिन वे आरक्षण हाथ में विषय के लिए जर्मन नहीं हैं।

संदर्भ वस्तुओं को संशोधित करने के निहितार्थ

  • यह समवर्ती प्रोग्रामिंग को और अधिक कठिन बनाता है - लेकिन सभी अनुप्रयोग मल्टीथ्रेडेड नहीं होते हैं
  • उन संशोधनों को आश्चर्यचकित किया जा सकता है, खासकर अगर विधि के बारे में कुछ भी नहीं बताता है कि ऐसा होगा
  • वे आश्चर्य रखरखाव को और अधिक कठिन बना सकते हैं

पुरानी विधि बनाम नई विधि

पुरानी पद्धति ने चीजों को जांचना आसान बना दिया:

  • GenerateUserName()स्वतंत्र रूप से परीक्षण योग्य है। आप उस पद्धति के खिलाफ परीक्षण लिख सकते हैं और सुनिश्चित कर सकते हैं कि नाम सही तरीके से उत्पन्न हुए हैं
  • यदि नाम को उपयोगकर्ता ऑब्जेक्ट से जानकारी की आवश्यकता है, तो आप हस्ताक्षर को बदल सकते हैं GenerateUserName(User user)और उस परीक्षण क्षमता को बनाए रख सकते हैं

नई विधि म्यूटेशन छुपाती है:

  • आप नहीं जानते कि Userजब तक आप 2 परतों को गहरा नहीं करते तब तक वस्तु बदल रही है
  • Userवस्तु के परिवर्तन उस मामले में अधिक आश्चर्यजनक हैं
  • SetUserName()उपयोगकर्ता नाम सेट करने से अधिक करता है। यह विज्ञापन में सच्चाई नहीं है जो नए डेवलपर्स के लिए यह जानना कठिन बनाता है कि आपके आवेदन में चीजें कैसे काम करती हैं

1

मैं जॉन वू के जवाब से पूरी तरह सहमत हूं। उनका सुझाव अच्छा है। लेकिन यह आपके प्रत्यक्ष प्रश्न को थोड़ा याद करता है।

मूल रूप से, किसी अन्य विधि से एक संपत्ति के मूल्य को खराब अभ्यास करने का अभ्यास है?

स्वाभाविक रूप से नहीं।

आप इसे बहुत दूर तक नहीं ले जा सकते, क्योंकि आप अप्रत्याशित व्यवहार में भाग लेंगे। जैसे PrintName(myPerson)व्यक्ति को ऑब्जेक्ट को बदलना नहीं चाहिए, क्योंकि विधि का अर्थ है कि यह केवल मौजूदा मूल्यों को पढ़ने में रुचि रखता है । लेकिन यह आपके मामले की तुलना में एक अलग तर्क है, क्योंकि SetUsername(user)इसका तात्पर्य यह है कि यह मूल्यों को स्थापित करने वाला है।


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

उदाहरण के लिए:

var myContract = CreateEmptyContract();

ArrrangeContractDeletedStatus(myContract);

मैं स्पष्ट रूप ArrrangeContractDeletedStatusसे myContractवस्तु की स्थिति को बदलने की विधि की अपेक्षा करता हूं ।

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

अगर मैं विलीन हो गया था CreateEmptyContractऔर ArrrangeContractDeletedStatusएक ही विधि में; मुझे हर अलग अनुबंध के लिए इस विधि के कई प्रकार बनाने होंगे जिन्हें मैं एक नष्ट स्थिति में परीक्षण करना चाहता हूं।

और जब मैं कुछ कर सकता था जैसे:

myContract = ArrrangeContractDeletedStatus(myContract);

यह या तो निरर्थक है (चूंकि मैं किसी भी तरह से वस्तु बदल रहा हूं), या मैं अब खुद को myContractवस्तु का गहरा क्लोन बनाने के लिए मजबूर कर रहा हूं ; यदि आप हर मामले को कवर करना चाहते हैं, तो यह अत्यधिक कठिन है (कल्पना करें कि मुझे कई स्तरों के नौसैनिक गुण चाहिए) क्या मुझे उन सभी का क्लोन बनाने की आवश्यकता है? केवल शीर्ष स्तर की इकाई? ... इतने सारे प्रश्न, इतने सारे निहितार्थ? )

वस्तु को बदलना सबसे आसान तरीका है जो मैं चाहता हूं कि सिद्धांत के एक मामले के रूप में वस्तु को न बदलने से बचने के लिए और अधिक काम करना चाहिए।


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


0

मुझे पुरानी के साथ-साथ नई समस्या भी मिल रही है।

पुराना तरीका:

1) InsertUser(User user)

इस विधि का नाम पढ़ते हुए, मेरी IDE में पॉप अपिंग, पहली बात, जो मेरे दिमाग में आती है

उपयोगकर्ता को कहां डाला जाता है?

विधि का नाम पढ़ना चाहिए AddUserToContext। कम से कम, यह है, विधि आखिर में क्या करती है।

2) InsertUser(User user)

यह स्पष्ट रूप से कम से कम आश्चर्य के सिद्धांत का उल्लंघन करता है। कहो, मैंने अब तक अपना काम किया और हौसले की मिसाल कायम की Userऔर उसे एक दिया nameऔर एक सेट किया password, मैं इससे बहुत प्रभावित होगा:

a) यह केवल उपयोगकर्ता को किसी चीज़ में सम्मिलित नहीं करता है

बी) यह भी उपयोगकर्ता के रूप में डालने के लिए मेरा इरादा नहीं है; नाम और पासवर्ड को ओवरराइड किया गया था।

ग) क्या यह इंगित करता है, कि यह एकल जिम्मेदारी सिद्धांत का भी उल्लंघन है।

नया रास्ता:

1) InsertUser(User user)

अभी भी एसआरपी का उल्लंघन और कम से कम आश्चर्य का सिद्धांत

2) SetUsername(User user)

सवाल

उपयोगकर्ता नाम सेट करें? किसका?

बेहतर: SetRandomNameया ऐसा कुछ जो इरादे को दर्शाता है।

3) SetPassword(User user)

सवाल

पासवर्ड सेट करें? किसका?

बेहतर: SetRandomPasswordया ऐसा कुछ जो इरादे को दर्शाता है।


सुझाव:

मैं जो पढ़ना पसंद करूंगा वह कुछ इस तरह होगा:

public User GenerateRandomUser(UserFactory Uf){
    User u = Uf.GetUser();
    u.Name = GenerateRandomUserName();
    u.Password = GenerateRandomUserPassword();
    return u;
}

...

public void AddUserToContext(User u){
    this.context.Users.Add(u);
}

आपके प्रारंभिक प्रश्न के बारे में:

क्या किसी वस्तु को संदर्भ द्वारा पारित करना एक बुरा अभ्यास है?

नहीं, यह स्वाद की बात है।

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