How to design an immutable object with complex initialization

后端 未结 6 1151
北恋
北恋 2021-02-02 15:59

I\'m learning about DDD, and have come across the statement that \"value-objects\" should be immutable. I understand that this means that the objects state should not change aft

6条回答
  •  逝去的感伤
    2021-02-02 16:48

    You can use reflection in order to initialize all the fields of the object and laziness to make "setter" like methods (using monadic functional style) in order to chain the set methods/functions together.

    For example:

    You can use this base class:

    public class ImmutableObject
    {
        private readonly Func>> initContainer;
    
        protected ImmutableObject() {}
    
        protected ImmutableObject(IEnumerable> 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>> init)
        {
            initContainer = init;
        }
    
        protected T setProperty(string propertyName, object propertyValue, bool lazy = true)
        {
    
            Func>> mergeFunc = delegate
                                                                            {
                                                                                var propertyDict = initContainer == null ? ObjectToDictonary () : initContainer();
                                                                                return propertyDict.Select(p => p.Key == propertyName? new KeyValuePair(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(mergeFunc()));
        }
    
        private IEnumerable> ObjectToDictonary()
        {
            var fields = GetType().GetFields().Where(f=> f.IsPublic);
            return fields.Select(f=> new KeyValuePair(f.Name, f.GetValue(this))).ToList();
        }
    
        private static object DictonaryToObject(IEnumerable> 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(properties);
        }
    }
    

    Can be implemented like so:

    public class State:ImmutableObject
    {
        public State(){}
        public State(IEnumerable> properties):base(properties) {}
        public State(Func>> 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);
        }
    }
    

    and can be used like this:

    //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();
    

提交回复
热议问题