qué objetos método mutables inmutables inmutabilidad clases c# domain-driven-design immutability

método - objetos inmutables c#



Cómo diseñar un objeto inmutable con inicialización compleja (6)

Estoy aprendiendo acerca de DDD y me he encontrado con la afirmación de que los "objetos de valor" deberían ser inmutables. Entiendo que esto significa que el estado de los objetos no debería cambiar después de haber sido creado. Esta es una especie de nueva forma de pensar para mí, pero tiene sentido en muchos casos.

Ok, entonces empiezo a crear objetos de valor inmutables.

  • Me aseguro de que toman todo el estado como parámetros para el constructor,
  • No agrego entidades de propiedad,
  • y asegúrese de que ningún método tenga permiso para modificar el contenido (solo devuelva instancias nuevas).

Pero ahora quiero crear este objeto de valor que contendrá 8 valores numéricos diferentes. Si creo un constructor que tenga 8 parámetros numéricos, creo que no será muy fácil de usar, o más bien, será fácil cometer un error al pasar los números. Esto no puede ser un buen diseño.

Entonces la pregunta es: ¿hay alguna otra forma de mejorar mi objeto inmutable ... alguna magia que se pueda hacer en C # para superar una larga lista de parámetros en el constructor? Estoy muy interesado en escuchar tus ideas ...

ACTUALIZACIÓN: Antes de que alguien lo mencione, se ha discutido una idea aquí: Patrón de objeto inmutable en C #: ¿qué piensas?

Sin embargo, estaría interesado en escuchar otras sugerencias o comentarios.


Por el momento, tendrías que usar un constructor con muchos argumentos o un constructor. En C # 4.0 (VS2010), puede usar argumentos nombrados / opcionales para lograr algo similar a los inicializadores de objetos C # 3.0 - vea aquí . El ejemplo en el blog es:

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

Pero puede ver fácilmente cómo algo similar puede aplicarse a cualquier constructor (u otro método complejo). Compare con la sintaxis del inicializador de objetos C # 3.0 (con un tipo mutable):

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

No hay mucho para distinguirlos, realmente.

Jon Skeet también ha publicado algunas reflexiones sobre este tema aquí .


Aunque probablemente sea parte del dominio de lo que está haciendo, y por lo tanto mi sugerencia puede ser inválida, ¿qué hay sobre intentar desglosar los 8 parámetros en grupos lógicos?

Cada vez que veo montones de parámetros, siento que el objeto / método / contructor debería ser más simple.


Fuera de mi cabeza, me vienen a la mente dos respuestas diferentes ...

... el primero, y probablemente el más simple, es usar una fábrica de objetos (o constructor) como ayudante que te asegure hacer las cosas bien.

La inicialización del objeto se vería así:

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

... el segundo es crear su objeto como un objeto convencional, mutable, con una función de Bloqueo () o Congelar (). Todos sus mutadores deberían verificar si el objeto ha sido bloqueado y lanzar una excepción si lo tiene.

La inicialización del objeto se vería así:

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

El método a seguir depende mucho de su contexto: una fábrica tiene la ventaja de ser conveniente si tiene una serie de objetos similares para construir, pero introduce otra clase para escribir y mantener. Un objeto bloqueable significa que solo hay una clase, pero otros usuarios pueden tener errores de tiempo de ejecución inesperados y las pruebas son más difíciles.


Usa un constructor

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

Luego créelo como:

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

Si algunos de los parámetros son opcionales, no necesitará llamar al método WithXXX y pueden tener valores predeterminados.


Puede usar la reflexión para inicializar todos los campos del objeto y la pereza para hacer métodos tipo "setter" (usando el estilo funcional monádico) para encadenar los métodos / funciones establecidos juntos.

Por ejemplo:

Puedes usar esta clase base:

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); } }

Se puede implementar así:

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); } }

y puede usarse así:

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


Me he quedado boquiabierto con la misma pregunta que los constructores complejos, también es un mal diseño para mí. Tampoco soy un gran admirador del concepto de constructor, ya que parece demasiado código adicional para mantener. Lo que necesitamos es inmutabilidad de paleta, lo que significa que un objeto comienza como mutable donde se le permite usar los establecedores de propiedades. Cuando se establecen todas las propiedades, debe haber una forma de congelar el objeto en un estado inmutable. Desafortunadamente, esta estrategia no se admite de forma nativa en el lenguaje C #. Por lo tanto, terminé diseñando mi propio patrón para crear objetos inmutables como se describe en esta pregunta:

Patrón de objeto inmutable en C #: ¿qué piensas?

Anders Hejlsberg está hablando de apoyo para este tipo de inmutabilidad a partir de las 36:30 en la siguiente entrevista:

Experto a experto: Anders Hejlsberg - El futuro de C #