كيفية تصميم كائن غير قابل للتغيير مع مجمع التهيئة

StackOverflow https://stackoverflow.com/questions/355172

سؤال

أنا التعلم عن DDD و قد تأتي عبر البيان أن "قيمة الأشياء" يجب أن تكون ثابتة.أنا أفهم أن هذا يعني أن الأشياء لا تتغير بعد ذلك تم إنشاؤه.هذا هو نوع من طريقة جديدة من التفكير بالنسبة لي ، ولكن من المنطقي في كثير من الحالات.

حسنا, لقد البدء في إنشاء ثابتة القيمة الكائنات.

  • لقد تأكد أنها تأخذ الدولة بأكملها كما المعلمات إلى منشئ ،
  • لا إضافة خاصية واضعي ،
  • و تأكد من عدم وجود طرق يسمح تعديل المحتوى (فقط عودة جديدة الحالات).

ولكن الآن أريد أن إنشاء هذه القيمة الكائن الذي سوف تحتوي على 8 قيم رقمية مختلفة.إن إنشاء منشئ وجود 8 معلمات رقمية أشعر أنه لن يكون من السهل جدا للاستخدام, أو بدلا من ذلك - فإنه سيكون من السهل أن تجعل من الخطأ عندما يمر في الأرقام.هذا لا يمكن أن تكون جيدة التصميم.

حتى الأسئلة هو: هل هناك أي طرق أخرى لجعل بلدي ثابتة الكائن أفضل.., أي السحر الذي يمكن القيام به في C# للتغلب على مسافة طويلة المعلمة القائمة في البناء ؟ أنا مهتم جدا في سماع أفكارك..

تحديث: قبل أي شخص يذكر أنه واحد الفكرة قد نوقشت هنا:ثابتة كائن نمط في C# - ماذا تعتقد ؟

قد تكون مهتمة في السمع أخرى أو اقتراحات أو تعليقات على الرغم من.

هل كانت مفيدة؟

المحلول

استخدام منشئ:

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 طريقة يمكن أن يكون القيم الافتراضية.

نصائح أخرى

في هذه اللحظة عليك استخدام منشئ مع الكثير من وسائط ، أو البناء.في C# 4.0 (VS2010) ، يمكنك استخدام اسمه/وسيطات اختيارية لتحقيق شيء مماثل C# 3.0 وجوه المهيآت - انظر هنا.على سبيل المثال في المدونة:

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

ولكن يمكنك أن ترى بسهولة كيف أن شيئا مماثلا يمكن أن تنطبق على أي منشئ (أو غيرها من طريقة معقدة).مقارنة C# 3.0 وجوه مهيئ الجملة (مع قابلة للتغيير نوع):

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

ليس كثيرا أن أقول لهم بصرف النظر حقا.

جون السكيت وقد نشرت بعض الأفكار حول هذا الموضوع أيضا ، هنا.

من على قمة رأسي اثنين إجابات مختلفة تتبادر إلى الذهن ...

...الأولى, وربما أبسط ، هو استخدام كائن مصنع (أو البناء) كما المساعد الذي يضمن لك الحصول على الأمور في نصابها الصحيح.

تهيئة كائن تبدو مثل هذا:

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

...والثاني هو إنشاء كائن التقليدية ، قابلة للتغيير ، الكائن مع قفل() أو تجميد وظيفة ().كل mutators أن تحقق لمعرفة ما إذا كان الكائن قد تم إغلاقه و رمي استثناء إذا كان لديه.

تهيئة كائن تبدو مثل هذا:

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

وهو أسلوب يعتمد كثيرا على السياق الخاص بك - مصنع لديها ميزة كونها مريحة إذا كان لديك سلسلة من الكائنات المماثلة لبناء, ولكنه أعرض فئة أخرى لكتابة والحفاظ عليها.قابلة للقفل كائن يعني أن هناك فئة واحدة فقط ، ولكن المستخدمين الآخرين غير متوقعة قد تحصل أخطاء وقت التشغيل والاختبار هو أصعب.

على الرغم من أنه ربما جزء من مجال ما تقومون به ، وبالتالي اقتراحي قد تكون غير صالحة ، ماذا عن محاولة لكسر 8 المعلمات إلى مجموعات منطقية?

كلما أرى أكوام من المعلمات ، أشعر وكأنني كائن/الطريقة/contructor يجب أن يكون أكثر بساطة.

لقد تحير مع نفس السؤال معقدة منشئات هو أيضا تصميم سيئة بالنسبة لي.أنا أيضا لا مروحة كبيرة من البناء المفهوم يبدو مثل الكثير من رمز إضافية للحفاظ على.ما نحتاجه هو المصاصة ثبات ، مما يعني أن الجسم يبدأ قابلة للتغيير حيث يسمح لك استخدام الخاصية واضعي.عندما يتم تعيين الخصائص يجب أن يكون هناك طريقة تجميد الكائن في ثابتة الدولة.هذه الاستراتيجية هو للأسف غير معتمد أصلا في لغة C#.ولذلك انتهى تصميم نمط بلدي لخلق الكائنات ثابتة كما هو موضح في هذا السؤال:

ثابتة كائن نمط في C# - ماذا تعتقد ؟

أندرس هيلسبرغ يتحدث عن دعم هذا النوع من ثبات 36:30 في المقابلة التالية:

خبير الخبراء:أندرس هيلسبرغ - مستقبل C#

يمكنك استخدام التفكير من أجل تهيئة كافة الحقول من وجوه والكسل لجعل "واضع" مثل أساليب (باستخدام بعملية نمط وظيفي) من أجل سلسلة مجموعة طرق/عمل معا.

على سبيل المثال:

يمكنك استخدام هذه الفئة الأساسية:

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();
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top