एपीआई डिजाइन में "बहुत सारे मापदंडों" समस्या से कैसे बचें?


160

मेरे पास यह एपीआई फ़ंक्शन है:

public ResultEnum DoSomeAction(string a, string b, DateTime c, OtherEnum d, 
     string e, string f, out Guid code)

मुझे यह पसंद नहीं है। क्योंकि पैरामीटर ऑर्डर अनावश्यक रूप से महत्वपूर्ण हो जाता है। नए क्षेत्रों को जोड़ना कठिन हो जाता है। यह देखना कठिन है कि आसपास क्या हो रहा है। यह छोटे भागों में परावर्तक विधि के लिए कठिन है क्योंकि यह उप कार्यों में सभी मापदंडों को पारित करने का एक और ओवरहेड बनाता है। कोड पढ़ना कठिन है।

मैं सबसे स्पष्ट विचार के साथ आया हूं: डेटा को इनकैप्सुलेट करने वाली एक वस्तु है और प्रत्येक पैरामीटर को एक-एक करके पास करने के बजाय इसे पास करें। यहां वह है जो मैंने जुटाया:

public class DoSomeActionParameters
{
    public string A;
    public string B;
    public DateTime C;
    public OtherEnum D;
    public string E;
    public string F;        
}

इससे मेरी API घोषणा कम हो गई:

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code)

अच्छा लगा। बहुत मासूम लगता है लेकिन हमने वास्तव में एक बहुत बड़ा बदलाव पेश किया है: हमने पारस्परिकता का परिचय दिया। क्योंकि जो हम पहले कर रहे थे वह वास्तव में एक गुमनाम अपरिवर्तनीय वस्तु को पारित करने के लिए था: स्टैक पर फ़ंक्शन पैरामीटर। अब हमने एक नया वर्ग बनाया, जो बहुत ही परिवर्तनशील है। हमने कॉल करने वाले की स्थिति में हेरफेर करने की क्षमता बनाई । वह चूसता है। अब मुझे अपनी वस्तु अपरिवर्तनीय चाहिए, मैं क्या करूँ?

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }        

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, 
     string e, string f)
    {
        this.A = a;
        this.B = b;
        // ... tears erased the text here
    }
}

जैसा कि आप देख सकते हैं कि मैंने वास्तव में अपनी मूल समस्या को फिर से बनाया है: बहुत सारे पैरामीटर। यह स्पष्ट है कि यह रास्ता नहीं है। मै क्या करने जा रहा हूँ? इस तरह की अपरिवर्तनीयता को प्राप्त करने का अंतिम विकल्प इस तरह "पठनीय" संरचना का उपयोग करना है:

public struct DoSomeActionParameters
{
    public readonly string A;
    public readonly string B;
    public readonly DateTime C;
    public readonly OtherEnum D;
    public readonly string E;
    public readonly string F;        
}

यह हमें कई मापदंडों के साथ कंस्ट्रक्टरों से बचने और अपरिवर्तनीयता प्राप्त करने की अनुमति देता है। वास्तव में यह सभी समस्याओं (पैरामीटर ऑर्डरिंग आदि) को ठीक करता है। फिर भी:

कि जब मैं भ्रमित हो गया और इस प्रश्न को लिखने का फैसला किया: क्या "सी कई मापदंडों" से बचने के लिए सी # में सबसे सीधा तरीका है, बिना म्यूटेशन शुरू किए? क्या उस उद्देश्य के लिए एक पठनीय संरचना का उपयोग करना संभव है और अभी तक खराब एपीआई डिजाइन नहीं है?

स्पष्टीकरण:

  • कृपया मान लें कि एकल जिम्मेदारीबिली सिद्धांत का कोई उल्लंघन नहीं है। मेरे मूल मामले में फ़ंक्शन केवल एक डीबी रिकॉर्ड के लिए दिए गए पैरामीटर लिखता है।
  • मैं दिए गए फ़ंक्शन के लिए एक विशिष्ट समाधान नहीं मांग रहा हूं। मैं ऐसी समस्याओं के लिए एक सामान्यीकृत दृष्टिकोण की मांग कर रहा हूं। मैं विशेष रूप से "बहुत सारे मापदंडों" समस्या को हल करने में रुचि रखता हूं, जो कि उत्परिवर्तन या एक भयानक डिजाइन को पेश किए बिना।

अपडेट करें

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


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

1
क्या आप बिल्डर-समाधान निरर्थक नहीं हैं यदि आप C # 4 का उपयोग करते हैं जहां आपके पास वैकल्पिक पैरामीटर हैं ?
22

1
मैं बेवकूफ हो सकता हूं, लेकिन मैं यह देखने में विफल रहता हूं कि यह एक समस्या कैसे है, यह देखते हुए कि यह एक DoSomeActionParametersफेंक वस्तु है, जिसे विधि कॉल के बाद छोड़ दिया जाएगा।
विल्क्स-

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

7
@ssg: मुझे भी अच्छा लगेगा। हमने सी # में उन विशेषताओं को जोड़ा है जो एक ही समय में अपरिवर्तनीयता (जैसे LINQ) को बढ़ावा देती हैं, क्योंकि हमने ऐसी विशेषताएं जोड़ी हैं जो उत्परिवर्तन को बढ़ावा देती हैं (जैसे ऑब्जेक्ट इनिशियलाइज़र।) यह अच्छा होगा कि अपरिवर्तनीय प्रकारों को बढ़ावा देने के लिए बेहतर सिंटैक्स हो। हम इसके बारे में कठिन सोच रहे हैं और कुछ दिलचस्प विचार हैं, लेकिन मैं अगले संस्करण के लिए ऐसी किसी भी चीज की उम्मीद नहीं करूंगा।
एरिक लिपर्ट

जवाबों:


22

ढाँचों में एक शैली आम तौर पर संबंधित मापदंडों को संबंधित वर्गों में समूहित करने की तरह है (लेकिन फिर से समस्या के साथ परस्परता के कारण):

var request = new HttpWebRequest(a, b);
var service = new RestService(request, c, d, e);
var client = new RestClient(service, f, g);
var resource = client.RequestRestResource(); // O params after 3 objects

सभी स्ट्रिंग को एक स्ट्रिंग ऐरे में संघनित करने से ही समझ में आता है यदि वे सभी संबंधित हैं। तर्कों के क्रम से, ऐसा लगता है कि वे पूरी तरह से संबंधित नहीं हैं।
icktoofay

सहमत, इसके अलावा, यह केवल तभी काम करेगा जब आपके पास पैरामीटर के समान बहुत सारे प्रकार होंगे।
टिमो विल्मसेन

प्रश्न में यह मेरे पहले उदाहरण से कैसे भिन्न है?
सेदत कपपनोग्लू

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

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

87

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

public class Param
{
        public string A { get; private set; }
        public string B { get; private set; }
        public string C { get; private set; }


  public class Builder
  {
        private string a;
        private string b;
        private string c;

        public Builder WithA(string value)
        {
              a = value;
              return this;
        }

        public Builder WithB(string value)
        {
              b = value;
              return this;
        }

        public Builder WithC(string value)
        {
              c = value;
              return this;
        }

        public Param Build()
        {
              return new Param { A = a, B = b, C = c };
        }
  }


  DoSomeAction(new Param.Builder()
        .WithA("a")
        .WithB("b")
        .WithC("c")
        .Build());

+1 - इस तरह का दृष्टिकोण (जिसे मैंने आमतौर पर "धाराप्रवाह इंटरफ़ेस" कहा जाता है) ठीक वैसा ही है जैसा कि मेरे मन में भी था।
डैनियल प्राइडेन

+1 - यह वही है जो मैंने उसी स्थिति में समाप्त किया है। सिवाय इसके कि सिर्फ एक वर्ग DoSomeActionथा , और यह एक तरीका था।
विल्क्स- ०

+1 मैंने भी इस बारे में सोचा था, लेकिन जब से मैं धाराप्रवाह इंटरफेस में नया हूं, मुझे नहीं पता था कि क्या यह यहां सही है। मेरे अपने अंतर्ज्ञान को मान्य करने के लिए धन्यवाद।
मैट

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

1
@ssg, यह इन परिदृश्यों में आम है। बीसीएल के पास कनेक्शन स्ट्रिंग बिल्डर कक्षाएं हैं, उदाहरण के लिए।
सैमुअल नेफ

10

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


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

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

2
+1 @ssg: हर बार जब मैं कभी इस तरह से अपने आप को समझाने में कामयाब रहा, तो मैंने समय के साथ बड़े स्तर पर उपयोगी अमूर्त ड्राइविंग करके खुद को गलत साबित कर दिया है। इवांस की डीडीडी पुस्तक आपको इस बारे में सोचने के बारे में कुछ विचार दे सकती है (हालांकि आपका सिस्टम ऐसा लगता है कि यह संभावित रूप से इस तरह के पेटेंट के आवेदन के लिए प्रासंगिक स्थान से बहुत दूर है) - (और यह सिर्फ एक शानदार किताब है)।
रूबेन बार्टलिंक

3
@ रूबेन: कोई भी सेंस बुक यह नहीं कहती है कि "एक अच्छे ओओ डिज़ाइन में एक क्लास में 5 से अधिक प्रॉपर्टीज़ नहीं होनी चाहिए"। कक्षाओं को तार्किक रूप से वर्गीकृत किया गया है और इस तरह का संदर्भ मात्रा पर आधारित नहीं हो सकता है। हालाँकि, C # की अपरिवर्तनीयता सहायता अच्छे OO डिज़ाइन सिद्धांतों का उल्लंघन शुरू करने से पहले गुणों की एक निश्चित संख्या में समस्याएं पैदा करना शुरू कर देती है ।
सेदत कपपनोग्लू

3
@ रूबेन: मैंने आपके ज्ञान, रवैये या स्वभाव का न्याय नहीं किया। मैं आपसे यही उम्मीद करता हूं। मैं यह नहीं कह रहा हूं कि आपकी सिफारिशें अच्छी नहीं हैं। मैं कह रहा हूं कि मेरी समस्या सबसे सही डिजाइन पर भी दिखाई दे सकती है और यह यहां एक अनदेखी बात प्रतीत होती है। हालाँकि मैं समझता हूं कि अनुभवी लोग सबसे सामान्य गलतियों के बारे में मौलिक प्रश्न क्यों पूछते हैं, एक दो राउंड के बाद इसे स्पष्ट करने में कम आनंद मिलता है। मुझे फिर से यह कहना चाहिए कि उस समस्या का एक संपूर्ण डिजाइन के साथ होना बहुत संभव है। और upvote के लिए धन्यवाद!
सेदत कपपनोग्लू

10

बस एक से अपने पैरामीटर डेटा संरचना को बदलने के classएक करने के लिए structऔर आप अच्छा जाने के लिए।

public struct DoSomeActionParameters 
{
   public string A;
   public string B;
   public DateTime C;
   public OtherEnum D;
   public string E;
   public string F;
}

public ResultEnum DoSomeAction(DoSomeActionParameters parameters, out Guid code) 

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

पेशेवरों:

  • लागू करने के लिए सबसे आसान
  • अंतर्निहित यांत्रिकी में व्यवहार का कम से कम परिवर्तन

विपक्ष:

  • अपरिहार्यता स्पष्ट नहीं है, डेवलपर ध्यान देने की आवश्यकता है।
  • अपरिवर्तनीयता बनाए रखने के लिए अनावश्यक नकल
  • जगह-जगह ढेर लगे

+1 यह सत्य है लेकिन "सीधे खेतों को ख़राब करना" के बारे में भाग को हल नहीं करता है। मुझे यकीन नहीं है कि अगर मैं एपीआई पर गुणों के बजाय खेतों का उपयोग करने का विकल्प चुनता हूं तो बुरी चीजें कैसे खराब हो सकती हैं।
सेदत कपपनोग्लू

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

ओह, यह सच है। बहुत बढ़िया! धन्यवाद।
सेदत कपपनोग्लू

8
मुझे लगता है कि ऑब्जेक्ट-ओरिएंटेड डिज़ाइन में एक्सपोज़िंग-फील्ड्स-इज़-बैड रूल वस्तुओं पर लागू होता है। जिस संरचना का मैं प्रस्ताव कर रहा हूं, वह मापदंडों का एक नंगे धातु का कंटेनर है। चूंकि आपकी वृत्ति किसी अपरिवर्तनीय चीज के साथ जाने की थी, इसलिए मुझे लगता है कि इस अवसर के लिए ऐसा बुनियादी कंटेनर उपयुक्त हो सकता है।
जेफरी एल व्हिटलेज

1
@JeffreyLWhitledge: मैं वास्तव में इस विचार को नापसंद करता हूं कि उजागर-क्षेत्र संरचनाएं बुराई हैं। इस तरह का दावा मुझे यह कहने के बराबर करता है कि स्क्रूड्राइवर्स बुरे हैं और लोगों को हथौड़ों का इस्तेमाल करना चाहिए क्योंकि स्क्रू ड्रायर्स के बिंदु नेल हेड हैं। यदि किसी को नाखून चलाने की आवश्यकता है, तो एक को हथौड़ा का उपयोग करना चाहिए, लेकिन अगर किसी को एक स्क्रू ड्राइव करने की आवश्यकता है, तो एक को एक पेचकश का उपयोग करना चाहिए। ऐसी कई परिस्थितियाँ हैं जहाँ एक उजागर-क्षेत्र संरचना ठीक काम के लिए सही उपकरण है। संयोग से, वहाँ बहुत कम हैं जहां एक गुण प्राप्त / सेट गुणों के साथ वास्तव में सही उपकरण है (अधिकांश मामलों में ...
सुपरकाट

6

कैसे अपने डेटा वर्ग के अंदर एक बिल्डर वर्ग बनाने के बारे में। डेटा क्लास में सभी सेटर निजी होंगे और केवल बिल्डर ही उन्हें सेट कर पाएंगे।

public class DoSomeActionParameters
    {
        public string A { get; private set; }
        public string B  { get; private set; }
        public DateTime C { get; private set; }
        public OtherEnum D  { get; private set; }
        public string E  { get; private set; }
        public string F  { get; private set; }

        public class Builder
        {
            DoSomeActionParameters obj = new DoSomeActionParameters();

            public string A
            {
                set { obj.A = value; }
            }
            public string B
            {
                set { obj.B = value; }
            }
            public DateTime C
            {
                set { obj.C = value; }
            }
            public OtherEnum D
            {
                set { obj.D = value; }
            }
            public string E
            {
                set { obj.E = value; }
            }
            public string F
            {
                set { obj.F = value; }
            }

            public DoSomeActionParameters Build()
            {
                return obj;
            }
        }
    }

    public class Example
    {

        private void DoSth()
        {
            var data = new DoSomeActionParameters.Builder()
            {
                A = "",
                B = "",
                C = DateTime.Now,
                D = testc,
                E = "",
                F = ""
            }.Build();
        }
    }

1
+1 यह एक पूरी तरह से वैध समाधान है, लेकिन मुझे लगता है कि इस तरह के एक सरल डिजाइन निर्णय को बनाए रखने के लिए यह बहुत अधिक मचान है। खासकर जब "पठनीय संरचना" समाधान आदर्श के करीब "बहुत" है।
सेदत कपपनोग्लू

2
यह "बहुत सारे मापदंडों" समस्या को कैसे हल करता है? वाक्य-विन्यास अलग हो सकता है, लेकिन समस्या समान दिखती है। यह एक आलोचना नहीं है, मैं सिर्फ उत्सुक हूं क्योंकि मैं इस पैटर्न से परिचित नहीं हूं।
alexD

1
@alexD इस समस्या को बहुत अधिक फ़ंक्शन पैरामीटर के साथ हल करता है और ऑब्जेक्ट को अपरिवर्तनीय रखता है। केवल बिल्डर वर्ग ही निजी गुण सेट कर सकता है और एक बार जब आप पैरामीटर ऑब्जेक्ट प्राप्त कर लेते हैं तो आप उसे बदल नहीं सकते हैं। समस्या यह है कि बहुत सारे मचान कोड की आवश्यकता है।
Marto

5
आपके समाधान में पैरामीटर ऑब्जेक्ट अपरिवर्तनीय नहीं है। कोई व्यक्ति जो बिल्डर को बचाता है, भवन निर्माण के बाद भी मापदंडों को संपादित कर सकता है
ast

1
@ एस्टीफ के बिंदु पर, जैसा कि वर्तमान में लिखा गया है कि एक DoSomeActionParameters.Builderउदाहरण का उपयोग ठीक एक DoSomeActionParametersउदाहरण बनाने और कॉन्फ़िगर करने में किया जा सकता है । बाद के Build()परिवर्तनों को कॉल करने के बाद गुणों Builderको मूल DoSomeActionParameters आवृत्ति के गुणों को संशोधित करना जारी रहेगा , और बाद की कॉल Build()उसी DoSomeActionParametersआवृत्ति को वापस करना जारी रखेगी । यह वास्तव में होना चाहिए public DoSomeActionParameters Build() { var oldObj = obj; obj = new DoSomeActionParameters(); return oldObj; }
BACON

6

मैं C # प्रोग्रामर नहीं हूं, लेकिन मेरा मानना ​​है कि C # नामित तर्कों का समर्थन करता है: (F # does और C # काफी हद तक इस तरह की चीज के लिए अनुकूल है) यह करता है: http://msdn.microsoft.com/en-us/library/dd264439 .aspx # Y342

इसलिए आपका मूल कोड कॉलिंग बन जाता है:

public ResultEnum DoSomeAction( 
 e:"bar", 
 a: "foo", 
 c: today(), 
 b:"sad", 
 d: Red,
 f:"penguins")

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

संपादित करें: यहाँ एक कलात्मक मैं इसके बारे में पाया गया है। http://www.globalnerdy.com/2009/03/12/default-and-onym-parameters-in-c-40-sith-lord-in-training/ मुझे C # 4.0 नाम के तर्कों का समर्थन करना चाहिए, 3.0 नहीं किया


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

6

क्यों न केवल एक इंटरफ़ेस बनाया जाए जो अपरिवर्तनीयता को लागू करता है (यानी केवल गेटर्स)?

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

public interface IDoSomeActionParameters
{
    string A { get; }
    string B { get; }
    DateTime C { get; }
    OtherEnum D { get; }
    string E { get; }
    string F { get; }              
}

public class DoSomeActionParameters: IDoSomeActionParameters
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }        
}

और फ़ंक्शन घोषणा बन जाती है:

public ResultEnum DoSomeAction(IDoSomeActionParameters parameters, out Guid code)

पेशेवरों:

  • structसमाधान जैसी स्टैक स्पेस समस्या नहीं है
  • भाषा के शब्दार्थ का उपयोग करते हुए प्राकृतिक समाधान
  • अपरिहार्यता स्पष्ट है
  • लचीले (उपभोक्ता चाहे तो एक अलग वर्ग का उपयोग कर सकता है)

विपक्ष:

  • कुछ दोहराए जाने वाले काम (दो अलग-अलग संस्थाओं में समान घोषणाएं)
  • डेवलपर को यह अनुमान लगाना होगा कि DoSomeActionParametersएक वर्ग है जिसे मैप किया जा सकता हैIDoSomeActionParameters

+1, मुझे नहीं पता कि मैंने ऐसा क्यों नहीं सोचा? :) मुझे लगता है कि मुझे लगा कि वस्तु अभी भी कई मापदंडों के साथ एक कंस्ट्रक्टर से पीड़ित होगी लेकिन ऐसा नहीं है। हाँ यह एक बहुत ही मान्य समाधान है। केवल एक समस्या जिसके बारे में मैं सोच सकता हूं कि यह एपीआई उपयोगकर्ता के लिए सही वर्ग नाम खोजने के लिए वास्तव में सीधा नहीं है जो दिए गए इंटरफ़ेस का समर्थन करता है। कम से कम प्रलेखन की आवश्यकता वाले समाधान बेहतर है।
सेदत कपपनोग्लू

मुझे यह पसंद है, मानचित्र के दोहराव और ज्ञान को पुनर्परिभाषित किया जाता है, और मैं कंक्रीट क्लास के डिफ़ॉल्ट निर्माता का उपयोग करके डिफ़ॉल्ट मानों की आपूर्ति कर सकता हूं
एंथनी जॉन्सटन

2
मुझे वह तरीका पसंद नहीं है। किसी ऐसे व्यक्ति का जिसके पास मनमाने ढंग से कार्यान्वयन का संदर्भ है IDoSomeActionParametersऔर उसमें मूल्यों को पकड़ना चाहता है, यह जानने का कोई तरीका नहीं है कि क्या संदर्भ को धारण करना पर्याप्त होगा, या यदि उसे मूल्यों को किसी अन्य ऑब्जेक्ट में कॉपी करना होगा। पठनीय इंटरफेस कुछ संदर्भों में उपयोगी होते हैं, लेकिन चीजों को अपरिवर्तनीय बनाने के साधन के रूप में नहीं।
सुपरकाट

"IDoSomeActionParameters पैरामीटर्स" को DoSomeActionParameters पर कास्ट किया जा सकता है और बदला जा सकता है। डेवलपर को यह भी एहसास नहीं हो सकता है कि वे मापदंडों को लागू करने के प्रयास को दरकिनार कर रहे हैं
जोसेफ सिम्पसन

3

मुझे पता है कि यह एक पुराना प्रश्न है, लेकिन मुझे लगा कि मैं अपने सुझाव के साथ उतारा करूँगा क्योंकि मुझे बस उसी समस्या को हल करना था। अब, मेरी समस्या आपके लिए थोड़ी अलग थी, क्योंकि मुझे उपयोगकर्ताओं को इस ऑब्जेक्ट को स्वयं बनाने में सक्षम नहीं होने की अतिरिक्त आवश्यकता थी (डेटा का सभी हाइड्रेशन डेटाबेस से आया था, इसलिए मैं आंतरिक रूप से सभी निर्माण को बंद कर सकता था)। इसने मुझे एक निजी कंस्ट्रक्टर और निम्नलिखित पैटर्न का उपयोग करने की अनुमति दी;

    public class ExampleClass
    {
        //create properties like this...
        private readonly int _exampleProperty;
        public int ExampleProperty { get { return _exampleProperty; } }

        //Private constructor, prohibiting construction outside of this class
        private ExampleClass(ExampleClassParams parameters)
        {                
            _exampleProperty = parameters.ExampleProperty;
            //and so on... 
        }

        //The object returned from here will be immutable
        public ExampleClass GetFromDatabase(DBConnection conn, int id)
        {
            //do database stuff here (ommitted from example)
            ExampleClassParams parameters = new ExampleClassParams()
            {
                ExampleProperty = 1,
                ExampleProperty2 = 2
            };

            //Danger here as parameters object is mutable

            return new ExampleClass(parameters);    

            //Danger is now over ;)
        }

        //Private struct representing the parameters, nested within class that uses it.
        //This is mutable, but the fact that it is private means that all potential 
        //"damage" is limited to this class only.
        private struct ExampleClassParams
        {
            public int ExampleProperty { get; set; }
            public int AnotherExampleProperty { get; set; }
            public int ExampleProperty2 { get; set; }
            public int AnotherExampleProperty2 { get; set; }
            public int ExampleProperty3 { get; set; }
            public int AnotherExampleProperty3 { get; set; }
            public int ExampleProperty4 { get; set; }
            public int AnotherExampleProperty4 { get; set; } 
        }
    }

2

आप एक बिल्डर-शैली दृष्टिकोण का उपयोग कर सकते हैं, हालांकि आपकी DoSomeActionपद्धति की जटिलता के आधार पर , यह एक स्पर्श हैवीवेट हो सकता है। इन पंक्तियों के साथ कुछ:

public class DoSomeActionParametersBuilder
{
    public string A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public OtherEnum D { get; set; }
    public string E { get; set; }
    public string F { get; set; }

    public DoSomeActionParameters Build()
    {
        return new DoSomeActionParameters(A, B, C, D, E, F);
    }
}

public class DoSomeActionParameters
{
    public string A { get; private set; }
    public string B { get; private set; }
    public DateTime C { get; private set; }
    public OtherEnum D { get; private set; }
    public string E { get; private set; }
    public string F { get; private set; }

    public DoSomeActionParameters(string a, string b, DateTime c, OtherEnum d, string e, string f)
    {
        A = a;
        // etc.
    }
}

// usage
var actionParams = new DoSomeActionParametersBuilder
{
    A = "value for A",
    C = DateTime.Now,
    F = "I don't care for B, D and E"
}.Build();

result = foo.DoSomeAction(actionParams, out code);

आह, मार्टो ने मुझे बिल्डर के सुझाव पर हरा दिया!
क्रिस व्हाइट

2

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

 BOOL WINAPI CreateProcess(
   __in_opt     LPCTSTR lpApplicationName,
   __inout_opt  LPTSTR lpCommandLine,
   __in_opt     LPSECURITY_ATTRIBUTES lpProcessAttributes,
   __in_opt     LPSECURITY_ATTRIBUTES lpThreadAttributes,
   __in         BOOL bInheritHandles,
   __in         DWORD dwCreationFlags,
   __in_opt     LPVOID lpEnvironment,
   __in_opt     LPCTSTR lpCurrentDirectory,
   __in         LPSTARTUPINFO lpStartupInfo,
   __out        LPPROCESS_INFORMATION lpProcessInformation
 );

तथा

 pid_t fork()
 int execvpe(const char *file, char *const argv[], char *const envp[])
 ...

जो लोग बच्चे के सृजन को नहीं जानते हैं, उनके लिए यह आसान हो सकता है:

pid_t child = fork();
if (child == 0) {
    execl("/bin/echo", "Hello world from child", NULL);
} else if (child != 0) {
    handle_error();
}

प्रत्येक डिज़ाइन विकल्प ट्रेड-ऑफ का प्रतिनिधित्व करता है कि वह कौन से संचालन कर सकता है।

पुनश्च। हां - यह बिल्डर के समान है - केवल रिवर्स में (यानी कॉलर के बजाय कैली की तरफ)। इस विशिष्ट मामले में बिल्डर बेहतर हो सकता है या नहीं।


यह एक छोटा सा ऑपरेशन है लेकिन यह किसी के लिए भी एक अच्छी सिफारिश है जो एकल-जिम्मेदारी सिद्धांत का उल्लंघन कर रहा है। अन्य सभी डिजाइन सुधार किए जाने के बाद ही मेरा सवाल है।
सेदत कपानोग्लू

यूपीएस। क्षमा करें - मैंने आपके संपादन से पहले एक प्रश्न तैयार किया और बाद में पोस्ट किया - इसलिए मैंने इसे नोटिस नहीं किया था।
मैकीज पीचोटका

2

यहाँ माइक से थोड़ा अलग है लेकिन मैं जो करने की कोशिश कर रहा हूँ वह पूरी तरह से संभव है कि जितना संभव हो उतना कम लिखें

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }

    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }

        public DoSomeActionParameters Create()
        {
            return new DoSomeActionParameters(this);
        }
    }
}

DoSomeActionParameters अपरिवर्तनीय है क्योंकि यह हो सकता है और इसे सीधे नहीं बनाया जा सकता क्योंकि इसका डिफ़ॉल्ट कंस्ट्रक्टर निजी है

इनिशियलाइज़र अपरिवर्तनीय नहीं है, लेकिन केवल एक परिवहन है

उपयोग इनिशियलाइज़र पर इनिशियलाइज़र का लाभ उठाता है (यदि आपको मेरा बहाव मिल जाता है) और मैं प्रारंभिक डिफ़ॉल्ट कंस्ट्रक्टर में चूक कर सकता है

DoSomeAction(new DoSomeActionParameters.Initializer
            {
                A = "Hello",
                B = 42
            }
            .Create());

यहां पैरामीटर वैकल्पिक होंगे, अगर आप चाहते हैं कि कुछ आवश्यक हो तो आप उन्हें शुरुआती डिफाल्टर कंस्ट्रक्टर में डाल सकते हैं

और सत्यापन बनाएँ विधि में जा सकता है

public class Initializer
{
    public Initializer(int b)
    {
        A = "(unknown)";
        B = b;
    }

    public string A { get; set; }
    public int B { get; private set; }

    public DoSomeActionParameters Create()
    {
        if (B < 50) throw new ArgumentOutOfRangeException("B");

        return new DoSomeActionParameters(this);
    }
}

तो अब ऐसा लग रहा है

DoSomeAction(new DoSomeActionParameters.Initializer
            (b: 42)
            {
                A = "Hello"
            }
            .Create());

अभी भी थोड़ा कूकी मुझे पता है, लेकिन वैसे भी कोशिश करने जा रहा हूं

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

public class DoSomeActionParameters
{
    readonly string _a;
    readonly int _b;

    public string A { get { return _a; } }
    public int B{ get { return _b; } }

    DoSomeActionParameters(Initializer data)
    {
        _a = data.A;
        _b = data.B;
    }

    public class Initializer
    {
        public Initializer()
        {
            A = "(unknown)";
            B = 88;
        }

        public string A { get; set; }
        public int B { get; set; }
    }

    public static DoSomeActionParameters Create(Action<Initializer> assign)
    {
        var i = new Initializer();
        assign(i)

        return new DoSomeActionParameters(i);
    }
}

तो अब कॉल इस तरह दिखता है

DoSomeAction(
        DoSomeActionParameters.Create(
            i => {
                i.A = "Hello";
            })
        );

1

संरचना का उपयोग करें, लेकिन सार्वजनिक क्षेत्रों के बजाय, सार्वजनिक गुण हैं:

• हर कोई (एफएक्सकॉप और जॉन स्कीट सहित) सहमत हैं कि सार्वजनिक क्षेत्रों को उजागर करना बुरा है।

जॉन और एफएक्सकोप को संतृप्त किया जाएगा क्योंकि आप उचित क्षेत्रों को उजागर नहीं कर रहे हैं।

• एरिक लिपर्ट एट अल कहते हैं कि अपरिवर्तनीयता के लिए पठनीय क्षेत्रों पर निर्भर रहना एक झूठ है।

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

    private bool propC_set=false;
    private date pC;
    public date C {
        get{
            return pC;
        }
        set{
            if (!propC_set) {
               pC = value;
            }
            propC_set = true;
        }
    }

एक अर्ध-अपरिवर्तनीय वस्तु (मान सेट किया जा सकता है लेकिन बदला नहीं गया)। मूल्य और संदर्भ प्रकारों के लिए काम करता है।


+1 शायद निजी वाचिक क्षेत्र और सार्वजनिक गेट-ओनली गुणों को कम वर्बोज़ समाधान की अनुमति देने के लिए एक संरचना में जोड़ा जा सकता है।
सेदत कपपनोग्लू

सार्वजनिक पठन-पाठन संरचनाओं पर गुण जो उत्परिवर्तन thisसार्वजनिक क्षेत्रों की तुलना में कहीं अधिक दुष्ट हैं। मुझे यकीन नहीं है कि आपको लगता है कि केवल एक बार मूल्य निर्धारित करने से चीजें ठीक हो जाती हैं; यदि कोई किसी संरचना के डिफ़ॉल्ट उदाहरण के साथ शुरू होता है, तो this-mutating गुणों से जुड़ी समस्याएं अभी भी मौजूद होंगी।
सुपरकाट

0

सैमुअल के जवाब का एक प्रकार जो मैंने अपनी परियोजना में उपयोग किया था जब मुझे एक ही समस्या थी:

class MagicPerformer
{
    public int Param1 { get; set; }
    public string Param2 { get; set; }
    public DateTime Param3 { get; set; }

    public MagicPerformer SetParam1(int value) { this.Param1 = value; return this; }
    public MagicPerformer SetParam2(string value) { this.Param2 = value; return this; }
    public MagicPerformer SetParam4(DateTime value) { this.Param3 = value; return this; }

    public void DoMagic() // Uses all the parameters and does the magic
    {
    }
}

और उपयोग करने के लिए:

new MagicPerformer().SeParam1(10).SetParam2("Yo!").DoMagic();

मेरे मामले में पैरामीटर जानबूझकर परिवर्तनशील थे, क्योंकि सेटर विधियां सभी संभव संयोजनों के लिए अनुमति नहीं देती थीं, और उनमें से केवल सामान्य संयोजनों को उजागर करती थीं। ऐसा इसलिए है क्योंकि मेरे कुछ पैरामीटर बहुत जटिल थे और सभी संभावित मामलों के लिए लेखन विधियां कठिन और अनावश्यक थीं (पागल संयोजन शायद ही कभी उपयोग किए जाते हैं)।


यह एक परिवर्तनशील वर्ग है, भले ही।
सेदत कपपनोग्लू

@ssg - ठीक है, हाँ, यह है। DoMagicअपने अनुबंध हालांकि यह वस्तु में बदलाव नहीं करेगी कि में है। लेकिन मुझे लगता है कि यह आकस्मिक संशोधनों से रक्षा नहीं करता है।
विल्क्स-
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.