Как создать неизменяемый объект со сложной инициализации

голоса
13

Я учусь о DDD, и наткнулся на утверждении, что «стоимость-объекты» должны быть неизменны. Я понимаю, что это означает, что состояние объекта не должно измениться после того, как он был создан. Это своего рода нового способа мышления для меня, но это имеет смысл во многих случаях.

Итак, я начинаю создавать незыблемые ценности-объекты.

  • Я убедиться, что они принимают все государство в качестве параметров конструктора,
  • Я не добавить сеттеры собственности,
  • и убедитесь, что никакие методы не могут изменять содержимое (возвращать только новые экземпляры).

Но теперь я хочу, чтобы создать этот объект значения, которое будет содержать 8 различных числовых значений. Если создать конструктор, имеющий 8 цифровых параметров я чувствую, что это не будет очень проста в использовании, а точнее - это будет легко сделать ошибку при прохождении в цифрах. Это не может быть хорошим дизайном.

Таким образом, вопросы есть: Существуют ли какие - либо другие способы сделать мой неизменный объект лучше .., любая магия , которая может быть сделано в C # , чтобы преодолеть длинный список параметров в конструкторе? Мне очень интересно услышать ваши идеи ..

UPDATE: Прежде чем кто - либо упоминает о нем, одна идее обсуждается здесь: Неизменный шаблон объекта в C # - что вы думаете?

Было бы интересно услышать другие предложения или комментарии, хотя.

Задан 10/12/2008 в 06:46
источник пользователем
На других языках...                            


7 ответов

голоса
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, и они могут иметь значения по умолчанию.

Ответил 10/12/2008 в 06:56
источник пользователем

голоса
9

На данный момент, вы должны использовать конструктор с большим количеством аргументов, или строителем. В 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" };

Не так много, чтобы сказать им друг от друга, на самом деле.

Джон Скит опубликовал некоторые мысли на эту тему тоже здесь .

Ответил 10/12/2008 в 09:30
источник пользователем

голоса
3

С верхней части моей головы, два разных ответа на ум приходят ...

... первый, и, вероятно, самый простой, заключается в использовании объекта завод (или строитель) в качестве помощника, который гарантирует вам получить вещи правильно.

Инициализация объекта будет выглядеть следующим образом:

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

... во-вторых, чтобы создать свой объект как обычный, изменчивый, объект с функцией блокировки () или Freeze (). Все ваши мутаторов должны проверить, чтобы увидеть, если объект был заблокирован, и выбросить исключение, если у него есть.

Инициализация объекта будет выглядеть следующим образом:

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

Какой метод принять во многом зависит от контекста - завод имеет то преимущество, что удобно, если у вас есть целый ряд подобных объектов, чтобы построить, но это действительно вводит еще один класс, чтобы писать и поддерживать. Блокируемый объект означает, что есть только один класс, но и другие пользователи могут получить неожиданные ошибки во время выполнения, и тестирование сложнее.

Ответил 10/12/2008 в 07:12
источник пользователем

голоса
1

Вы можете использовать отражение для того, чтобы инициализировать все поля объекта и лени, чтобы сделать «сеттер», как методы (с использованием монадического функционального стиля), чтобы приковать методы набора / функции вместе.

Например:

Вы можете использовать этот базовый класс:

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();
Ответил 26/06/2014 в 14:17
источник пользователем

голоса
1

Я пугался с тем же вопросом, как сложные конструкторы также плохой дизайн для меня. Я тоже не большой поклонник концепции строителя, как это кажется, что слишком много дополнительного кода, чтобы сохранить. Что нам нужно, это эскимо неизменность, что означает, что объект начинает как изменяемые, где разрешено использовать сеттеры собственности. Когда все свойства устанавливаются там должен быть способ замораживания объекта в неизменном состоянии. Эта стратегия, к сожалению, не поддерживается изначально в C # языке. Поэтому я в конечном итоге проектирование своего собственного шаблона для создания неизменяемых объектов, как описано в этом вопросе:

Неизменный шаблон объекта в C # - что вы думаете?

Хейлсберг говорит о поддержке этого типа неизменности от 36:30 в следующем интервью:

Эксперт Эксперт: Хейлсберг - будущее C #

Ответил 06/05/2009 в 07:23
источник пользователем

голоса
1

Хотя это, вероятно, часть домена, что вы делаете, и, таким образом, мое предложение может быть недействительным, что о попытке сломать 8 параметров в логические группы?

Всякий раз, когда я вижу кучу параметров, я чувствую, как объект / метод / застройщик должен быть проще.

Ответил 10/12/2008 в 09:40
источник пользователем

голоса
0

Taka взгляд на библиотеку Ремут https://github.com/ababik/Remute

Вы можете произвести новый неизменяемый объект применения лямбда-выражения к существующей. Нет генерацию коды или котел код пластинчатых Builder шаблона.

Например

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

Он также работает с вложенными неизменными структурами.

Ответил 24/02/2018 в 17:45
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more