2008-12-10 11 views
13

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

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

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

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

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

UPDATE: Прежде чем кто упоминает об этом, одна идея обсуждалась здесь: Immutable object pattern in C# - what do you think?

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

+2

Вы также должны сделать все поля только для чтения. Это делает неизменность более декларативной. – JaredPar 2008-12-10 05:57:09

ответ

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

Этот шов является хорошим вариантом. Какой-то дополнительный код, но я думаю, что вы получаете некоторую ясность. Я попробую это. – 2008-12-10 06:06:39

+3

Недостатком этого является то, что вы не получаете преимущества инициализаторов объектов C# 3. Это может быть исправлено с помощью свойств (а также методов для клиентов pre-C# 3): new Entity.Builder {Field1 = 123, Field2 = 456, Field3 = 789} .Build() – 2008-12-10 06:25:58

+0

Спасибо Jon - I hadn ' я думал об этом подходе, поскольку я застрял в 1.1 и 2.0 мире на работе и только недавно начал играть с C# 3 дома. – 2008-12-10 06:41:34

3

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

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

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

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. 

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

8

На данный момент вам придется использовать конструктор с большим количеством аргументов или строителем. В C# 4.0 (VS2010) вы можете использовать аргументы named/optional для достижения чего-то похожего на инициализаторы объектов C# 3.0 - см. here. Пример на блоге:

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

Но вы можете легко увидеть, как что-то подобное можно применить для любого конструктора (или другого комплексного метода). По сравнению с C# 3.0 объектно-инициализатора синтаксис (с изменяемым типом):

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

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

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

1

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

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

1

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

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

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

1

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

Например:

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

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

Така взглянуть на Re mute library https://github.com/ababik/Remute

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

E.g.

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

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

 Смежные вопросы

  • Нет связанных вопросов^_^