Como desenhar um objeto imutável com inicialização complexo

votos
13

Eu estou aprendendo sobre DDD, e já se deparou com a afirmação de que valor-objetos devem ser imutáveis. Eu entendo que isso significa que o estado objetos não deve mudar após ele ter sido criado. Esta é uma espécie de uma nova maneira de pensar para mim, mas faz sentido em muitos casos.

Ok, então eu começar a criar valor-objetos imutáveis.

  • Ter certeza de que eles tomam o estado inteiro como parâmetros para o construtor,
  • Eu não adiciono setters de propriedade,
  • e certifique-se há métodos estão autorizados a modificar o conteúdo (somente retornar novas instâncias).

Mas agora eu quero criar esse objeto de valor que irá conter 8 valores numéricos diferentes. Se eu criar um construtor que tem 8 parâmetros numéricos que eu sinto que ele não vai ser muito fácil de usar, ou melhor - que vai ser fácil cometer um erro ao passar nos números. Isso não pode ser bom design.

Assim, as perguntas é: Existem outras maneiras de fazer o meu objeto imutável melhor .., qualquer mágica que pode ser feito em C # para superar uma lista de parâmetros de comprimento no construtor? Estou muito interessado em ouvir suas idéias ..

UPDATE: Antes que alguém o menciona, uma idéia tem sido discutida aqui: padrão de objeto imutável em C # - o que você acha?

Estaria interessado em ouvir outras sugestões ou comentários embora.

Publicado 10/12/2008 em 06:46
fonte usuário
Em outras línguas...                            


7 respostas

votos
22

Use um construtor:

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

Em seguida, crie-o como:

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

Se alguns dos parâmetros são opcionais você não precisará chamar o método WithXXX e podem ter valores padrão.

Respondeu 10/12/2008 em 06:56
fonte usuário

votos
9

No momento, você teria que usar um construtor com muitos argumentos, ou um construtor. No C # 4.0 (VS2010), você pode usar o nome / argumentos opcionais para conseguir algo semelhante ao C # 3.0 Object-initializers - veja aqui . O exemplo no blog é:

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

Mas você pode facilmente ver como algo semelhante pode aplicar para qualquer construtor (ou outro método complexo). Comparar com a sintaxe objeto-inicializador C # 3.0 (com um tipo mutável):

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

Não há muito a diferenciá-las, realmente.

Jon Skeet publicou algumas reflexões sobre este assunto também, aqui .

Respondeu 10/12/2008 em 09:30
fonte usuário

votos
3

Em cima da minha cabeça, duas respostas diferentes vêm à mente ...

... o primeiro, e provavelmente mais simples, é usar uma fábrica de objeto (ou construtor) como um auxiliar que garante que você fazer as coisas certas.

inicialização objeto seria algo como isto:

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

... o segundo é para criar o seu objeto como um convencional, mutável, objeto com a função de bloqueio () ou Freeze (). Todos os seus mutators deve verificar para ver se o objeto foi bloqueado, e lançar uma exceção se ele tem.

inicialização objeto seria algo como isto:

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

Qual o método para tomar depende muito de seu contexto - a fábrica tem a vantagem de ser conveniente se você tem uma série de objetos semelhantes para construir, mas introduz uma outra classe de escrever e manter. Um objeto bloqueável significa que há apenas uma classe, mas outros usuários pode ter erros de execução inesperados, e teste é mais difícil.

Respondeu 10/12/2008 em 07:12
fonte usuário

votos
1

Você pode usar a reflexão, a fim de inicializar todos os campos do objeto e preguiça para fazer "setter" como métodos (usando estilo funcional monádico), a fim de cadeia os métodos set / funções juntos.

Por exemplo:

Você pode usar esta classe 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);
    }
}

Pode ser implementado assim:

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

e pode ser usado como este:

//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();
Respondeu 26/06/2014 em 14:17
fonte usuário

votos
1

Tenho vindo a confundia com a mesma pergunta como construtores complexos também é design ruim para mim. Eu também não sou um grande fã do conceito construtor como parece que muito código extra para manter. O que precisamos é imutabilidade picolé, o que significa que um objeto começa como mutável, onde você tem permissão para usar os setters de propriedade. Quando todas as propriedades são definidas, deve haver uma maneira de congelar o objeto em um estado imutável. Esta estratégia infelizmente não é suportado nativamente na linguagem C #. Por isso, acabou projetando o meu próprio padrão para a criação de objetos imutáveis ​​como descrito nesta questão:

padrão de objeto imutável em C # - o que você acha?

Anders Hejlsberg está falando sobre o suporte para este tipo de imutabilidade de 36:30 na entrevista a seguir:

Especialista para especialista: Anders Hejlsberg - The Future of C #

Respondeu 06/05/2009 em 07:23
fonte usuário

votos
1

Embora seja provavelmente parte do domínio do que você está fazendo, e, portanto, a minha sugestão pode ser inválido, o que sobre a tentativa de quebrar os parâmetros 8 em grupos lógicos?

Sempre que vejo montes de parâmetros, eu sinto como se o objeto / método / contructor deveria ser mais simples.

Respondeu 10/12/2008 em 09:40
fonte usuário

votos
0

Taka uma olhada biblioteca Remute https://github.com/ababik/Remute

Você pode produzir novo objeto imutável aplicação expressão lambda para existente. Sem geração de código ou caldeira código placa como padrão Builder.

Por exemplo

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

Ele também funciona com estruturas imutáveis ​​aninhadas.

Respondeu 24/02/2018 em 17:45
fonte usuário

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