Pregunta

Estoy aprendiendo acerca de la DDD, y han llegado a través de la afirmación de que "valor-objetos" deben ser inmutables. Entiendo que esto significa que el estado de los objetos no debe cambiar después de que ha sido creado. Esta es una especie de una nueva forma de pensar para mí, pero tiene sentido en muchos casos.

Ok, así que empezar a crear valor-objetos inmutables.

  • Me aseguro que toman todo el estado como parámetros al constructor,
  • No agrego emisores de propiedad,
  • y asegúrese de que no se permiten los métodos de modificar el contenido (sólo volver nuevos casos).

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

Así que la pregunta es: ¿Hay otras formas de hacer mi mejor .. objeto inmutable, ningún tipo de magia que se puede hacer en C # para superar una larga lista de parámetros en el constructor? Estoy muy interesado en escuchar sus ideas ..

ACTUALIZACIÓN: Antes de que nadie lo menciona, una idea se ha discutido aquí: inmutable en C # - ¿qué te parece

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

¿Fue útil?

Solución

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

A continuación, cree que le gusta:

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

Si algunos de los parámetros son opcionales que no tendrá que llamar al método WithXXX y pueden tener valores por defecto.

Otros consejos

Por el momento, tendría que utilizar un constructor con una gran cantidad de argumentos, o un constructor. En C # 4.0 (VS2010), puede utilizar el nombre / argumentos opcionales para lograr algo similar a C # 3.0 inicializadores de objeto - vea aquí . El ejemplo en el blog es:

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

Sin embargo, se puede ver fácilmente cómo se puede aplicar algo similar para cualquier constructor (u otro método complejo). Comparar con la sintaxis objeto-inicializador C # 3.0 (con un tipo mutable):

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

No hay mucho que distinguirlos, de verdad.

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

De la parte superior de mi cabeza, dos respuestas diferentes vienen a la mente ...

... la primera, y probablemente más simple, es el uso de una factoría de objetos (o constructor) como un ayudante que le asegura que hacer las cosas bien.

Inicialización de objetos se vería así:

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

... la segunda es crear su objeto como, mutable, objeto convencional con una función de bloqueo () o Congelar (). Todos sus mutators debe comprobar para ver si el objeto está bloqueado, y lanzar una excepción si tiene.

Inicialización de objetos se vería así:

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

¿Qué método para tomar depende mucho de su contexto - una fábrica tiene la ventaja de ser conveniente si usted tiene una serie de objetos similares a construir, pero sí introducir otra clase de escribir y mantener. Un objeto con llave significa que hay una sola clase, pero otros usuarios podría obtener errores inesperados en tiempo de ejecución, y la prueba es más difícil.

A pesar de que es probable que sea parte del dominio de lo que está haciendo, y por lo tanto mi sugerencia puede no ser válido, ¿qué pasa con el intento de romper los 8 parámetros en grupos lógicos?

Cada vez que veo un montón de parámetros, me siento como el objeto / método / contructor debería ser más sencillo.

He estado boggled con la misma pregunta que los constructores complejos es también un mal diseño para mí. Asimismo, no soy un gran fan del concepto constructor, ya que parece como demasiado código adicional de mantener. Lo que necesitamos es la inmutabilidad de paleta, lo que significa que un objeto empieza como mutable donde se le permite utilizar los emisores de propiedad. Cuando todas las propiedades se establecen que debe haber una manera de congelar el objeto en un estado inmutable. Esta estrategia, lamentablemente no es compatible de forma nativa en el lenguaje C #. Por lo tanto, terminé el diseño de mi propio patrón para la creación de objetos inmutables como se describe en esta pregunta:

modelo objeto inmutable en C # - ¿qué te parece?

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

expertos para expertos: Anders Hejlsberg - El futuro de C #

Puede utilizar la reflexión con el fin de inicializar todos los campos del objeto y la pereza de hacer "colocador" como métodos (el uso de estilo funcional monádico) con el fin de la cadena de los métodos set / funciones juntos.

Por ejemplo:

Puede utilizar esta clase de 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);
    }
}

Puede ser implementado de esta manera:

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 ser utilizado de esta manera:

//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();
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top