Frage

Ich lerne über DDD, und sind dabei auf die Aussage kommen, dass „Wert-Objekte“ unveränderlich sein sollte. Ich verstehe, dass dies bedeutet, dass die Objekte Zustand nicht ändern, nachdem es erstellt wurde. Dies ist eine Art eine neue Möglichkeit für mich zu denken, aber es macht Sinn in vielen Fällen.

Ok, so beginnen die Schaffung ich unveränderlichen Wert-Objekte.

  • Ich sicherstellen, dass sie den gesamten Zustand als Parameter an den Konstruktor übernehmen,
  • Ich füge nicht Eigentum Setter,
  • und stellen Sie sicher, sind keine Verfahren erlaubt den Inhalt (nur zurückgeben neue Instanzen) zu ändern.

Aber jetzt will ich diesen Wert Objekt erstellen, die 8 verschiedene numerische Werte enthalten. Wenn ich einen Konstruktor mit 8 numerischen Parameter erstellen Ich glaube, dass es nicht sehr einfach sein wird, zu verwenden, oder besser gesagt - wird es leicht sein, einen Fehler zu machen, wenn sie in den Zahlen vorbei. Das kann nicht gut ausgebildet sein.

So die Fragen ist: Gibt es noch andere Möglichkeiten mein unveränderliches Objekt besser zu machen .., jede Magie, die in C # getan werden kann, eine lange Parameterliste im Konstruktor zu überwinden? Ich bin sehr daran interessiert, Ihre Ideen zu hören ..

UPDATE: Bevor jemand es erwähnt, eine Idee wurde hier diskutiert: Immutable Objektmuster in C # - was denken Sie

Würde obwohl hören andere Vorschläge oder Anmerkungen interessiert sein.

War es hilfreich?

Lösung

Verwenden Sie einen Builder:

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

Dann erstellen Sie es mögen:

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

Wenn einige der Parameter optional sind Sie nicht die WithXXX Methode aufrufen müssen, und sie können Standardwerte haben.

Andere Tipps

Im Moment würden Sie einen Konstruktor mit vielen args oder Builder verwenden. In C # 4.0 (VS2010), können Sie Namen / optionale Argumente verwenden, um etwas ähnlich zu C # 3.0 Objekt-Initialisierer zu erreichen - siehe hier . Das Beispiel auf dem Blog ist:

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

Aber man kann leicht sehen, wie etwas ähnliches für jeden Konstruktor anwenden können (oder andere komplexe Verfahren). Vergleichen Sie mit der C # 3.0 Objekt-Initialisierer Syntax (mit einem wandelbaren Typ):

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

Nicht viel sie auseinander zu halten, wirklich.

Jon Skeet einige Gedanken zu diesem Thema auch geschrieben hat, hier .

Aus der Spitze von meinem Kopf, zwei verschiedene Antworten in dem Sinne kommen ...

... der erste und wahrscheinlich einfachste, ist ein Objekt Fabrik (oder Builder) als Helfer zu verwenden, die Sie Dinge richtig machen gewährleistet.

Objektinitialisierung würde wie folgt aussehen:

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

... das zweite ist das Objekt als ein herkömmliches, wandelbar, Objekt mit einer Sperre () oder Einfrieren () Funktion zu erstellen. Alle Ihre Mutatoren überprüfen sollten, ob das Objekt gesperrt wurde, und eine Ausnahme auslösen, wenn es hat.

Objektinitialisierung würde wie folgt aussehen:

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

Welche Methode hängt viel von Ihrem Kontext zu nehmen - eine Fabrik den Vorteil, bequem hat, wenn Sie eine Reihe von ähnlichen Objekten zu konstruieren, aber es hat eine andere Klasse einführen zu schreiben und zu pflegen. Ein abschließbarer Objekt bedeutet, dass es nur eine Klasse, aber auch andere Benutzer möglicherweise unerwartete Laufzeitfehler erhalten, und Testen ist härter.

Auch wenn es wahrscheinlich Teil der Domäne von dem, was man tut, und kann daher mein Vorschlag ungültig sein, was zu versuchen, die 8 Parameter in logische Gruppen zu brechen?

Immer wenn ich Haufen von Parametern zu sehen, ich fühle mich wie das Objekt / Methode / contructor soll einfacher sein.

Ich habe mit der gleichen Frage wie komplexer Konstrukteuren boggled worden ist auch schlechtes Design zu mir. Ich bin auch kein großer Fan des Erbauers Konzept, da es wie zu viel zusätzlichen Code scheint zu halten. Was wir brauchen, ist Eis am Stiel Unveränderlichkeit, was bedeutet, dass ein Objekt als wandelbar beginnt, wo Sie die Eigenschaft Setter nutzen dürfen. Wenn alle Eigenschaften festgelegt werden muss es eine Möglichkeit sein, das Objekt in einem unveränderlichen Zustand des Einfrierens. Diese Strategie ist leider nicht nativ in der Sprache C # unterstützt. Ich landete daher mein eigenes Muster der Gestaltung bis zum Erstellen von unveränderlichen Objekten wie in dieser Frage beschrieben:

Immutable Objektmuster in C # - was denken Sie?

Anders Hejlsberg spricht über Unterstützung für diese Art von Unveränderlichkeit von 36:30 im folgende Interview:

Experte Experte: Anders Hejlsberg - Die Zukunft von C #

Sie können Reflektion verwenden, um alle Felder des Objekts zu initialisieren und Faulheit „Setter“ wie Methoden (mit monadischen funktionalen Stil), um Kette die eingestellten Methoden / Funktionen zusammen zu machen.

Zum Beispiel:

Sie können diese Basisklasse verwenden:

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

Kann wie so umgesetzt werden:

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

und kann wie folgt verwendet werden:

//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();
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top