2008-12-10 11 views
13

मैं डीडीडी के बारे में सीख रहा हूं, और इस बयान में आया हूं कि "मूल्य-वस्तुएं" अपरिवर्तनीय होनी चाहिए। मैं समझता हूं कि इसका मतलब यह है कि वस्तुएं बनने के बाद वस्तुओं को बदलना नहीं चाहिए। यह मेरे लिए सोचने का एक नया तरीका है, लेकिन कई मामलों में यह समझ में आता है।जटिल प्रारंभिकरण के साथ एक अपरिवर्तनीय वस्तु को कैसे डिजाइन करें

ठीक है, इसलिए मैं अपरिवर्तनीय मूल्य-वस्तुएं बनाना शुरू कर देता हूं।

  • मुझे यकीन है कि वे निर्माता के लिए पैरामीटर के रूप में पूरे राज्य ले कर,
  • मैं संपत्ति setters नहीं जोड़ते हैं,
  • और सुनिश्चित करें कि कोई विधि (सामग्री को संशोधित करने के लिए केवल नए उदाहरणों लौट अनुमति दी जाती है)।

लेकिन अब मैं इस मान वस्तु को बनाना चाहता हूं जिसमें 8 अलग-अलग संख्यात्मक मान होंगे। यदि मैं 8 संख्यात्मक पैरामीटर वाले कन्स्ट्रक्टर का निर्माण करता हूं तो मुझे लगता है कि इसका उपयोग करना बहुत आसान नहीं होगा, या इसके बजाय - संख्याओं में गुजरने में गलती करना आसान होगा। यह अच्छा डिजाइन नहीं हो सकता है।

तो प्रश्न यह है: क्या मेरे अपरिवर्तनीय वस्तु को बेहतर बनाने का कोई अन्य तरीका है .., किसी भी जादू को कन्स्ट्रक्टर में एक लंबी पैरामीटर सूची को पार करने के लिए सी # में किया जा सकता है? मैं बहुत अपने विचारों को सुनने में रुचि है ..

अद्यतन: इससे पहले कि किसी को भी यह उल्लेख है, एक विचार यहाँ पर चर्चा की गई है: Immutable object pattern in C# - what do you think?

अन्य सुझाव या टिप्पणियां सुनवाई हालांकि में रुचि रखते हैं।

+2

आपको सभी फ़ील्ड केवल पढ़ने के लिए भी बनाना चाहिए। यह अपरिवर्तनीयता को अधिक घोषणात्मक बनाता है – JaredPar

उत्तर

22

एक निर्माता का उपयोग करें:

public class Entity 
{ 
    public class Builder 
    { 
    private int _field1; 
    private int _field2; 
    private int _field3; 

    public Builder WithField1(int value) { _field1 = value; return this; } 
    public Builder WithField2(int value) { _field2 = value; return this; } 
    public Builder WithField3(int value) { _field3 = value; return this; } 

    public Entity Build() { return new Entity(_field1, _field2, _field3); } 
    } 

    private int _field1; 
    private int _field2; 
    private int _field3; 

    private Entity(int field1, int field2, int field3) 
    { 
    // Set the fields. 
    } 

    public int Field1 { get { return _field1; } } 
    public int Field2 { get { return _field2; } } 
    public int Field3 { get { return _field3; } } 

    public static Builder Build() { return new Builder(); } 
} 

और फिर बनाएं इसे पसंद:

Entity myEntity = Entity.Build() 
        .WithField1(123) 
        .WithField2(456) 
        .WithField3(789) 
        .Build() 

कुछ पैरामीटर वैकल्पिक हैं, तो आप WithXXX विधि कॉल करने की जरूरत नहीं होगी और वे डिफ़ॉल्ट मान हो सकते हैं ।

+0

यह सीम एक अच्छा विकल्प बनने के लिए बनाता है। कुछ अतिरिक्त कोड, लेकिन मुझे लगता है कि आप कुछ स्पष्टता प्राप्त करते हैं। मैं इसे आज़माउंगा। –

+3

इसके साथ नकारात्मकता यह है कि आपको सी # 3 ऑब्जेक्ट प्रारंभकर्ताओं का लाभ नहीं मिलता है। इसे गुण (साथ ही पूर्व-सी # 3 क्लाइंट के लिए विधियों) के द्वारा तय किया जा सकता है: नई इकाई। बिल्डर {फ़ील्ड 1 = 123, फ़ील्ड 2 = 456, फ़ील्ड 3 = 78 9} .बिल्ड() –

+0

धन्यवाद जॉन - मैंने नहीं किया था ' इस दृष्टिकोण के बारे में सोचा नहीं क्योंकि मैं काम पर 1.1 और 2.0 दुनिया में फंस गया हूं और हाल ही में घर पर सी # 3 के साथ खेलना शुरू कर दिया है। –

3

मेरे सिर के ऊपर बंद, दो अलग-अलग जवाब मन के लिए आते हैं ...

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

वस्तु प्रारंभ इस प्रकार दिखाई देगा:

var factory = new ObjectFactory(); 
factory.Fimble = 32; 
factory.Flummix = "Nearly"; 
var mine = factory.CreateInstance(); 

... दूसरा एक पारंपरिक, परिवर्तनशील, एक लॉक() या फ्रीज() फ़ंक्शन के साथ वस्तु के रूप में अपने वस्तु बनाने के लिए है। आपके सभी म्यूटेटर्स को यह देखने के लिए जांच करनी चाहिए कि क्या ऑब्जेक्ट लॉक हो गया है, और यदि इसके पास अपवाद फेंक दिया गया है।

वस्तु प्रारंभ इस प्रकार दिखाई देगा:

var mine = new myImmutableObject(); 
mine.Fimble = 32; 
mine.Flummix = "Nearly"; 
mine.Lock(); // Now it's immutable. 

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

+1

ध्यान दें कि इन दोनों मामलों में, सी # 3.0 प्रारंभिकरण को अधिक कॉम्पैक्ट बनाता है। –

8

फिलहाल, आपको बहुत सारे तर्क, या निर्माता के साथ एक कन्स्ट्रक्टर का उपयोग करना होगा। सी # 4.0 (वीएस -2010) में, आप सी # 3.0 ऑब्जेक्ट-प्रारंभकर्ताओं के समान कुछ प्राप्त करने के लिए नामित/वैकल्पिक तर्कों का उपयोग कर सकते हैं - here देखें। ब्लॉग पर उदाहरण है:

Person p = new Person (forename: "Fred", surname: "Flintstone"); 

लेकिन आप आसानी से कैसे कुछ इसी तरह किसी भी निर्माता (या अन्य जटिल विधि) के लिए आवेदन कर सकते हैं देख सकते हैं। सी # 3.0 वस्तु प्रारंभकर्ता वाक्य रचना (एक परिवर्तनशील प्रकार के साथ) से तुलना करें:

Person p = new Person { Forename = "Fred", Surname = "Flintstone" }; 

ज्यादा नहीं उन्हें अलग से बताने, वास्तव में।

जॉन स्कीट ने इस विषय पर भी कुछ विचार पोस्ट किए हैं, here

+0

यदि आप सी # 4.0 का उपयोग करते हैं तो इस समस्या के लिए बिल्कुल सही समाधान! धन्यवाद! – rene

1

हालांकि यह संभवतः आपके द्वारा किए जा रहे कार्यों के डोमेन का हिस्सा है, और इस प्रकार मेरा सुझाव अमान्य हो सकता है, तार्किक समूहों में 8 पैरामीटर को तोड़ने का प्रयास करने के बारे में क्या?

जब भी मैं पैरामीटर के ढेर को देखता हूं, मुझे लगता है कि ऑब्जेक्ट/विधि/नियंत्रक को सरल होना चाहिए।

+0

मैंने पहले से ही बहुत कुछ किया है, और इस सिद्धांत का उपयोग करते हुए कि किसी वर्ग में 7 या 8 से अधिक फ़ील्ड नहीं होनी चाहिए, मैंने सुंदर लॉजिकल ग्रुपिंग के साथ कई मूल्य वस्तुओं के साथ समाप्त कर दिया है। लेकिन एक मूल्यवान टिप्पणी, Chii। –

1

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

Immutable object pattern in C# - what do you think?

एंडर्स हेल्स्बर्ग निम्नलिखित साक्षात्कार में 36:30 से अचल स्थिति के इस प्रकार के लिए समर्थन के बारे में बात कर रही है:

इसलिए मैं इस सवाल में वर्णित के रूप अपरिवर्तनीय वस्तुओं बनाने के लिए अपने खुद के पैटर्न डिजाइन करने समाप्त हो गया

Expert to Expert: Anders Hejlsberg - The Future of C#

1

आप सेट तरीकों/कार्य एक साथ श्रृंखला के लिए (monadic कार्यात्मक शैली का प्रयोग करके) क्रम में प्रतिबिंब का उपयोग तरीकों की तरह "सेटर" बनाने के लिए वस्तु और आलस्य के सभी क्षेत्रों को प्रारंभ कर सकते हैं।

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

आप इस आधार वर्ग का उपयोग कर सकते हैं:

public class ImmutableObject<T> 
{ 
    private readonly Func<IEnumerable<KeyValuePair<string, object>>> initContainer; 

    protected ImmutableObject() {} 

    protected ImmutableObject(IEnumerable<KeyValuePair<string,object>> properties) 
    { 
     var fields = GetType().GetFields().Where(f=> f.IsPublic); 

     var fieldsAndValues = 
      from fieldInfo in fields 
      join keyValuePair in properties on fieldInfo.Name.ToLower() equals keyValuePair.Key.ToLower() 
      select new {fieldInfo, keyValuePair.Value}; 

     fieldsAndValues.ToList().ForEach(fv=> fv.fieldInfo.SetValue(this,fv.Value)); 

    } 

    protected ImmutableObject(Func<IEnumerable<KeyValuePair<string,object>>> init) 
    { 
     initContainer = init; 
    } 

    protected T setProperty(string propertyName, object propertyValue, bool lazy = true) 
    { 

     Func<IEnumerable<KeyValuePair<string, object>>> mergeFunc = delegate 
                     { 
                      var propertyDict = initContainer == null ? ObjectToDictonary() : initContainer(); 
                      return propertyDict.Select(p => p.Key == propertyName? new KeyValuePair<string, object>(propertyName, propertyValue) : p).ToList(); 
                     }; 

     var containerConstructor = typeof(T).GetConstructors() 
      .First(ce => ce.GetParameters().Count() == 1 && ce.GetParameters()[0].ParameterType.Name == "Func`1"); 

     return (T) (lazy ? containerConstructor.Invoke(new[] {mergeFunc}) : DictonaryToObject<T>(mergeFunc())); 
    } 

    private IEnumerable<KeyValuePair<string,object>> ObjectToDictonary() 
    { 
     var fields = GetType().GetFields().Where(f=> f.IsPublic); 
     return fields.Select(f=> new KeyValuePair<string,object>(f.Name, f.GetValue(this))).ToList(); 
    } 

    private static object DictonaryToObject<T>(IEnumerable<KeyValuePair<string,object>> objectProperties) 
    { 
     var mainConstructor = typeof (T).GetConstructors() 
      .First(c => c.GetParameters().Count()== 1 && c.GetParameters().Any(p => p.ParameterType.Name == "IEnumerable`1")); 
     return mainConstructor.Invoke(new[]{objectProperties}); 
    } 

    public T ToObject() 
    { 
     var properties = initContainer == null ? ObjectToDictonary() : initContainer(); 
     return (T) DictonaryToObject<T>(properties); 
    } 
} 

इसलिए की तरह लागू किया जा सकता:

public class State:ImmutableObject<State> 
{ 
    public State(){} 
    public State(IEnumerable<KeyValuePair<string,object>> properties):base(properties) {} 
    public State(Func<IEnumerable<KeyValuePair<string, object>>> func):base(func) {} 

    public readonly int SomeInt; 
    public State someInt(int someInt) 
    { 
     return setProperty("SomeInt", someInt); 
    } 

    public readonly string SomeString; 
    public State someString(string someString) 
    { 
     return setProperty("SomeString", someString); 
    } 
} 

और इस तरह इस्तेमाल किया जा सकता:

//creating new empty object 
var state = new State(); 

// Set fields, will return an empty object with the "chained methods". 
var s2 = state.someInt(3).someString("a string"); 
// Resolves all the "chained methods" and initialize the object setting all the fields by reflection. 
var s3 = s2.ToObject(); 
0

एक नज़र डालें रिमोट लाइब्रेरी पर https://github.com/ababik/Remute

आप मौजूदा अपरिवर्तित ऑब्जेक्ट को लैम्ब्डा अभिव्यक्ति को लागू कर सकते हैं। बिल्डर पैटर्न जैसे कोड कोड या बॉयलर प्लेट कोड नहीं।

उदा।

var entity = new Entity(field1, field2, field3); 
entity = remute.With(entity, x => x.Field1, "foo"); 

यह नेस्टेड अपरिवर्तनीय संरचनाओं के साथ भी काम करता है।

संबंधित मुद्दे