Domanda

Sto imparando su DDD, e sono imbattuto l'affermazione che "valore-oggetti" devono essere immutabili. Capisco che questo significa che lo Stato gli oggetti non dovrebbe cambiare dopo che è stato creato. Questa è una specie di un nuovo modo di pensare per me, ma ha senso in molti casi.

Ok, così inizio a creare valore-oggetti immutabili.

  • I assicurarsi che prendono l'intero stato come parametri al costruttore,
  • non aggiungo setter di proprietà,
  • e assicurarsi che non i metodi sono autorizzati a modificare il contenuto (solo ritorno nuove istanze).

Ma ora voglio creare questo oggetto valore che conterrà 8 diversi valori numerici. Se creo un costruttore con 8 parametri numerici sento che non sarà molto facile da usare, o meglio - sarà facile fare un errore quando si passa nei numeri. Questo non può essere un buon design.

Quindi le domande è: Ci sono altri modi di fare il mio oggetto immutabile meglio .., nessuna magia che può essere fatto in C # per superare una lunga lista di parametri nel costruttore? Sono molto interessato a sentire le vostre idee ..

UPDATE: Prima che qualcuno ne parla, una sola idea è stata discussa qui: Immutabile in C # - che cosa ne pensi

sarebbe interessato a conoscere altri suggerimenti o commenti però.

È stato utile?

Soluzione

Utilizzare un costruttore:

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

Quindi creare le cose come:

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

Se alcuni dei parametri sono opzionali non sarà necessario chiamare il metodo WithXXX e possono avere valori di default.

Altri suggerimenti

Al momento, dovreste usare un costruttore con un sacco di argomenti, o di un costruttore. In C # 4.0 (VS2010), è possibile utilizzare il nome / argomenti opzionali per raggiungere qualcosa di simile a C # 3.0 Object-inizializzatori - vedere qui . L'esempio sul blog è:

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

Ma si può facilmente vedere come qualcosa di simile può applicare per qualsiasi costruttore (o altro metodo complesso). Confronto alla sintassi object-inizializzatore C # 3.0 (con un tipo mutabile):

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

Non c'è molto da dire loro a parte, davvero.

Jon Skeet ha pubblicato alcune riflessioni su questo argomento troppo, qui .

Al largo della parte superiore della mia testa, due risposte diverse vengono in mente ...

... il primo, e forse più semplice, è quello di utilizzare una fabbrica oggetto (o costruttore) come aiutante che vi permette di ricevere le cose a posto.

inizializzazione dell'oggetto sarebbe simile a questa:

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

... il secondo è quello di creare l'oggetto come un convenzionale, mutevole, oggetto di una funzione di blocco () o Fermo (). Tutti i tuoi mutators dovrebbe controllare per vedere se l'oggetto è stato bloccato, e un'eccezione se ha.

inizializzazione dell'oggetto sarebbe simile a questa:

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

Quale metodo di prendere dipende molto sul contesto - una fabbrica ha il vantaggio di essere conveniente se si dispone di una serie di oggetti simili a costruire, ma lo fa introdurre un'altra classe di scrivere e mantenere. Un oggetto con serratura significa che v'è una sola classe, ma altri utenti potrebbero avere errori di runtime imprevisti, e il test è più difficile.

Anche se è probabilmente parte del dominio di ciò che si sta facendo, e quindi il mio suggerimento potrebbe non essere valido, quello di tentare di abbattere i parametri 8 in gruppi logici?

Ogni volta che vedo un sacco di parametri, mi sento come l'oggetto / metodo / contructor dovrebbe essere più semplice.

Sono stato boggled con la stessa domanda di costruttori complessi è anche cattiva progettazione a me. Io non sono anche un grande fan del concetto costruttore come sembra come troppo codice aggiuntivo da mantenere. Ciò di cui abbiamo bisogno è ghiaccioli immutabilità, il che significa che un oggetto inizia come mutabile in cui si è permesso di usare il setter di proprietà. Quando tutte le proprietà sono impostate ci deve essere un modo di congelare l'oggetto in uno stato immutabile. Questa strategia non è purtroppo supportato nativamente nel linguaggio C #. Ho quindi finito per progettare il mio modello per la creazione di oggetti immutabili come descritto in questa domanda:

modello oggetto immutabile in C # - cosa ne pensi?

Anders Hejlsberg sta parlando di supporto per questo tipo di immutabilità dal 36:30 nella seguente intervista:

Expert all'esperto: Anders Hejlsberg - Il futuro di C #

È possibile utilizzare riflessione al fine di inizializzare tutti i campi dell'oggetto e la pigrizia di fare "setter" come i metodi (con stile funzionale monadica) al fine di catena i metodi set / funzioni insieme.

Ad esempio:

È possibile utilizzare questa classe di 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);
    }
}

Può essere implementato in questo modo:

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 può essere utilizzato in questo modo:

//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();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top