Question

J'apprends à propos de DDD, et je suis venu dans la déclaration que la « valeur-objets » devrait être immuable. Je comprends que cela signifie que l'état des objets ne doit pas changer après qu'il a été créé. Ce genre est d'une nouvelle façon de penser pour moi, mais il est logique dans de nombreux cas.

Ok, donc je commence à créer des objets de valeur immuable.

  • Je veille à ce qu'ils prennent tout l'État en tant que paramètres au constructeur,
  • Je n'ajoute pas setters de propriété,
  • et assurez-vous pas de méthodes sont autorisés à modifier le contenu (uniquement renvoyer de nouvelles instances).

Mais maintenant, je veux créer cet objet de valeur qui contiendra 8 différentes valeurs numériques. Si je crée un constructeur ayant 8 paramètres numériques je pense que ce ne sera pas très facile à utiliser, ou plutôt - il sera facile de faire une erreur lors du passage dans les chiffres. Cela ne peut pas être un bon design.

Ainsi, les questions est: sont mieux il d'autres moyens de faire mon objet immuable .., toute la magie qui peut être fait en C # pour surmonter une longue liste de paramètres dans le constructeur? Je suis très intéressé à entendre vos idées ..

Mise à jour: Avant que quiconque ne le mentionne, une idée a été discutée ici: Immuable en C # - Que pensez-vous

serait intéressé à entendre d'autres suggestions ou commentaires si.

Était-ce utile?

La solution

Utilisez un constructeur:

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

Ensuite, créez-le comme:

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

Si certains paramètres sont facultatifs, vous aurez pas besoin d'appeler la méthode WithXXX et ils peuvent avoir des valeurs par défaut.

Autres conseils

À l'heure actuelle, vous auriez à utiliser un constructeur avec beaucoup de args, ou un constructeur. En C # 4.0 (VS2010), vous pouvez utiliser le nom / arguments en option pour réaliser quelque chose de similaire à C # 3.0 objet-initializers - voir ici . L'exemple sur le blog est:

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

Mais vous pouvez facilement voir comment quelque chose de similaire peut demander pour tout constructeur (ou autre méthode complexe). Comparer la syntaxe objet initialiseur C # 3.0 (avec un type mutable):

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

Pas grand chose à leur dire à part, vraiment.

Jon Skeet a posté quelques réflexions sur ce sujet aussi, ici .

Du haut de ma tête, deux réponses différentes viennent à l'esprit ...

... la première, et probablement la plus simple, est d'utiliser une fabrique d'objets (ou le constructeur) comme aide qui vous assure les choses.

initialisation de l'objet ressemblerait à ceci:

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

... le deuxième est de créer votre objet en tant que classique, mutable, objet avec une fonction de verrouillage () ou Freeze (). Tous vos mutateurs doivent vérifier si l'objet a été verrouillé, et jeter une exception si elle a.

initialisation de l'objet ressemblerait à ceci:

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

Quelle méthode pour prendre dépend beaucoup de votre contexte - une usine a l'avantage d'être pratique si vous avez une série d'objets similaires à construire, mais elle introduit une autre classe à écrire et à maintenir. Un objet verrouillable signifie qu'il n'y a qu'une seule classe, mais d'autres utilisateurs pourraient obtenir des erreurs d'exécution inattendues, et le test est plus difficile.

Bien qu'il soit probablement partie du domaine de ce que vous faites, et ma suggestion peut donc être invalide, qu'en essayant de décomposer les 8 paramètres en groupes logiques?

Chaque fois que je vois des tas de paramètres, je me sens comme l'objet / méthode / contructor devrait être plus simple.

Je suis boggled avec la même question que les constructeurs complexes est également une mauvaise conception pour moi. Je suis pas un grand fan du concept de constructeur car il semble que trop de code supplémentaire pour maintenir. Ce que nous avons besoin est popsicle immuabilité, ce qui signifie qu'un objet commence mutable où vous êtes autorisé à utiliser les setters de propriété. Lorsque toutes les propriétés sont définies, il doit y avoir un moyen de geler l'objet dans un état immuable. Cette stratégie est malheureusement pas pris en charge en mode natif dans le langage C #. donc j'ai fini par concevoir mon propre modèle pour la création d'objets immuables comme décrit dans cette question:

modèle d'objet Immuable en C # - que pensez-vous?

Anders Hejlsberg parle de soutien à ce type d'immuabilité de 36:30 dans l'interview qui suit:

expert à expert: Anders Hejlsberg - L'avenir de C #

Vous pouvez utiliser la réflexion pour initialiser tous les champs de l'objet et la paresse de faire « poseur » comme les méthodes (en utilisant un style fonctionnel monadique) afin de chaîner les méthodes set / fonctions ensemble.

Par exemple:

Vous pouvez utiliser cette classe 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);
    }
}

Peut être mis en œuvre comme ceci:

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

et peut être utilisé comme ceci:

//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();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top