2010-12-15 117 views
9

J'ai regardé quelques vidéos de Greg Young récemment et j'essaie de comprendre pourquoi il y a une attitude négative envers les Setters sur les objets de domaine. Je pensais que les objets de domaine étaient censés être "lourds" avec de la logique dans DDD. Existe-t-il de bons exemples en ligne du mauvais exemple et ensuite, comment le corriger? Tous les exemples ou explications sont bons. Cela s'applique-t-il uniquement aux événements stockés de manière CQRS ou s'applique-t-il à tous les DDD?Conception dirigée par domaine, objets de domaine, attitude à propos de Setters

Répondre

11

Je contribue cette réponse pour compléter Roger Alsing répondre à propos des invariants avec d'autres raisons.

L'information sémantique

Roger clairement expliqué que les organismes de propriété ne portent pas d'information sémantique. Autoriser un setter pour une propriété comme Post.PublishDate peut ajouter de la confusion car nous ne pouvons pas savoir avec certitude si le post a été publié ou seulement si la date de publication a été définie. Nous ne pouvons pas savoir avec certitude que c'est tout ce qui est nécessaire pour publier un article. L'objet "interface" ne montre pas clairement son "intention".Cependant, je crois que des propriétés telles que "Enabled" ont une sémantique suffisante pour "get" et "setting". C'est quelque chose qui devrait être efficace immédiatement et je ne vois pas le besoin de méthodes SetActive() ou Activate()/Deactivate() pour la seule raison de la sémantique manquante sur le setter de la propriété.

Invariants

Roger a également parlé de la possibilité de briser par setters invariants propriété. C'est tout à fait vrai, et il faut faire des propriétés qui fonctionnent en tandem pour fournir une "valeur invariante combinée" (comme les angles de triangle pour utiliser l'exemple de Roger) comme propriétés en lecture seule et créer une méthode pour les définir ensemble, qui peut valider toutes les combinaisons en une seule étape.

initialisation de la commande de propriété/Définition des dépendances

Ceci est similaire au problème car il provoque invariants des problèmes avec des propriétés qui doivent être validées/modifiées ensemble. Imaginez le code suivant:

public class Period 
    { 
     DateTime start; 
     public DateTime Start 
     { 
      get { return start; } 
      set 
      { 
       if (value > end && end != default(DateTime)) 
        throw new Exception("Start can't be later than end!"); 
       start = value; 
      } 
     } 

     DateTime end; 
     public DateTime End 
     { 
      get { return end; } 
      set 
      { 
       if (value < start && start != default(DateTime)) 
        throw new Exception("End can't be earlier than start!"); 
       end = value; 
      } 
     } 
    } 

Ceci est un exemple naïf de validation "setter" qui provoque des dépendances d'ordre d'accès. Le code suivant illustre ce problème:

 public void CanChangeStartAndEndInAnyOrder() 
     { 
      Period period = new Period(DateTime.Now, DateTime.Now); 
      period.Start = DateTime.Now.AddDays(1); //--> will throw exception here 
      period.End = DateTime.Now.AddDays(2); 
      // the following may throw an exception depending on the order the C# compiler 
      // assigns the properties. 
      period = new Period() 
      { 
       Start = DateTime.Now.AddDays(1), 
       End = DateTime.Now.AddDays(2), 
      }; 
      // The order is not guaranteed by C#, so either way may throw an exception 
      period = new Period() 
      { 
       End = DateTime.Now.AddDays(2), 
       Start = DateTime.Now.AddDays(1), 
      }; 
     } 

Puisque nous ne pouvons pas changer la date de début après la date de fin sur un objet de période (à moins qu'il soit une période « vide », avec les deux dates fixées par défaut (DateTime) - oui, ce n'est pas un super design, mais vous comprenez ce que je veux dire ...) essayer de définir la date de début en premier fera une exception.

Cela devient plus sérieux lorsque nous utilisons des initialiseurs d'objets. Puisque C# ne garantit aucun ordre d'assignation, nous ne pouvons pas faire d'hypothèses sûres et le code peut ou non lancer une exception en fonction des choix du compilateur. MAL!

Ceci est finalement un problème avec la conception des classes. Comme la propriété ne peut pas "savoir" que vous mettez à jour les deux valeurs, elle ne peut "désactiver" la validation tant que les deux valeurs n'ont pas été modifiées. Vous devez soit rendre les deux propriétés en lecture seule et fournir une méthode pour définir les deux en même temps (perdre la fonctionnalité des initialiseurs d'objet) ou supprimer complètement le code de validation des propriétés (peut-être introduire une autre propriété en lecture seule comme IsValid ou valider si nécessaire).

ORM "hydratation" *

Hydratation, dans une vision simpliste, signifie obtenir les données persistantes en objets. Pour moi, c'est vraiment le plus gros problème avec l'ajout de la logique derrière les régleurs de propriétés. Beaucoup/la plupart ORM mappent la valeur persistante dans une propriété

Si vous avez une logique de validation ou une logique qui change l'état de l'objet (les autres membres) à l'intérieur des setters de la propriété, vous finirez par essayer de valider par rapport à un objet "incomplet" (un étant encore chargé). Ceci est très similaire au problème d'initialisation d'objet puisque vous ne pouvez pas contrôler l'ordre dans lequel les champs sont "hydratés". La plupart des ORM vous permettent de mapper la persistance à des champs privés au lieu de propriétés, ce qui permet d'hydrater les objets, mais si votre logique de validation se trouve principalement dans les propriétés, vous devrez peut-être la dupliquer ailleurs pour vérifier qu'une l'objet chargé est valide ou non.Étant donné que de nombreux outils ORM prennent en charge le chargement paresseux (aspect fondamental d'un ORM!) En utilisant des propriétés (ou méthodes) virtuelles, le mappage vers les champs rendra impossible le chargement par chargement accidentel d'objets dans des champs.

Conclusion

Ainsi, à la fin, afin d'éviter la duplication de code, permettent ORM d'effectuer le mieux qu'ils pouvaient, empêcher surprenantes exceptions en fonction de l'ordre des champs sont définis, il est sage de déplacer la logique loin des créateurs de propriété.

Je suis encore en train de déterminer où cette logique de 'validation' devrait être. Où validons-nous les aspects invariants d'un objet? Où mettons-nous des validations de plus haut niveau? Utilisons-nous des hooks sur les ORMs pour effectuer la validation (OnSave, OnDelete, ...)? Etc. Etc. Etc. Mais ce n'est pas la portée de cette réponse.

+1

donc dans votre exemple devrions-nous utiliser une seule fonction comme 'period.setDateBoundries (startDate, endDate)' pour éviter les setters? une seule fonction qui détient une signification commerciale au lieu de setter factice? – Songo

+1

@Songo c'est ce que je ferais. Mais alors on pourrait se plaindre d'avoir à passer les deux dates pour en changer une seule (imaginez si on voulait prolonger une période, on devrait passer les deux dates! ... mais alors, hé, pourquoi ne pas créer une méthode Extend? ?) Pensez comme ceci: chaque fois que vous finissez avec une propriété qui dépend d'un autre, alors peut-être c'est une bonne idée d'introduire des méthodes pour les changer ou des méthodes qui ajouteront plus de valeur sémantique (comme le Period.Extend ...)! – Loudenvier

+0

Je ne fais que passer des tableaux. Ce n'est pas génial pour les indices IDE, mais cela me permet de transmettre des données dans n'importe quel ordre. Je peux également ajouter ou omettre des données en fonction de la circonstance (c'est-à-dire la période complète, ou une seule date). La validation est faite de toute façon, il ne semble donc pas beaucoup plus difficile de vérifier si un paramètre a été donné ou omis. – prograhammer

1

Un setter définit simplement une valeur. Ce n'est pas censé être "heavy" with logic.

Les méthodes sur vos objets qui ont de bons noms descriptifs devraient être celles "heavy" with logic et avoir un analogue dans le domaine lui-même.

7

La raison derrière cela est que l'entité elle-même devrait être responsable de changer son état. Il n'y a pas vraiment de raison pour que vous deviez définir une propriété n'importe où en dehors de l'entité elle-même.

Un exemple simple serait une entité qui a un nom. Si vous avez un setter public, vous pouvez changer le nom d'une entité de n'importe où dans votre application. Si vous supprimez à la place ce setter et mettez une méthode comme ChangeName(string name) à votre entité qui sera le seul moyen de changer le nom. De cette façon, vous pouvez ajouter n'importe quel type de logique qui sera toujours exécuté lorsque vous changez le nom, car il n'y a qu'une seule façon de le changer. C'est aussi beaucoup plus clair que de simplement mettre le nom à quelque chose. Fondamentalement, cela signifie que vous exposer le comportement sur vos entités publiquement pendant que vous gardez l'état en privé.

+0

Et ce sera encore un setter. Mais dans ce cas vous avez une méthode (ou un consommateur de message) au lieu de la propriété et cela rend votre objet de domaine actif (pas seulement DTO). Et pourquoi vous avez besoin (ou pas besoin) de faire cela expliqué dans l'article http://martinfowler.com/bliki/AnemicDomainModel.html –

+0

@Vsevolod Parfenov, exactement. Maintenant, c'était un exemple très simple. Mais parfois vous devrez peut-être ajouter une sorte de logique à cette méthode. Peut-être que vous voulez aussi mettre à jour un autre champ. –

+0

Le SETTER pour une propriété est juste un sucre syntaxique pour une méthode. Si une propriété a une logique, elle fait également de votre objet de domaine un objet actif, pas seulement un DTO, parce qu'en fait vous venez de lui ajouter une méthode, même si elle est "cachée" en tant que setter de propriété. Vous n'auriez toujours qu'un seul endroit pour changer le nom de l'entité: le setter de la propriété Name. Donc, ce ne sont pas les raisons de l'attitude négative envers les créateurs de propriété. Il y a d'autres raisons (que je développerai dans une réponse): les invariants, la dépendance de séquence, les problèmes d'hydratation d'entités à partir d'un outil ORM. – Loudenvier

2

La question originale est étiquetée .net, donc je vais soumettre une approche pragmatique pour le contexte où vous souhaitez lier directement vos entités à une vue.

Je sais que c'est une mauvaise pratique, et que vous devriez probablement avoir un modèle de vue (comme dans MVVM) ou similaire, mais pour certaines petites applications, il est logique de ne pas trop modeler IMHO.

L'utilisation de propriétés est la manière dont la liaison de données prête à l'emploi fonctionne dans .net. Peut-être que le contexte stipule que la liaison de données devrait fonctionner dans les deux sens, et donc l'implémentation de INotifyPropertyChanged et l'augmentation de PropertyChanged en tant que partie de la logique de setter ont du sens.

L'entité pourrait, par ex. ajouter un élément à une collection de règles brisée ou similaire (je sais que l'AAPC avait ce concept il y a quelques années et peut-être quand même) lorsque le client définit une valeur invalide, et cette collection pourrait être affichée dans l'interface utilisateur. L'unité de travail refuserait plus tard de conserver les objets invalides, si cela devait arriver aussi loin. J'essaie de justifier le découplage, l'immuabilité, et ainsi de suite dans une large mesure. Je dis simplement que dans certains contextes, une architecture plus simple est nécessaire.

+0

+1 Je me bats avec le concept pour les raisons exactes que vous avez énumérées. Suis familier avec le MVVM Silverlight et l'ancienne expérience avec certains CSLA. J'adorerais voir les commentaires d'autres développeurs .NET. – BuddyJoe

10

Setters ne porte aucune information sémantique.

par exemple.

blogpost.PublishDate = DateTime.Now; 

Est-ce que cela signifie que le message a été publié? Ou simplement que la date de publication a été définie?

Tenir compte:

blogpost.Publish(); 

Cela montre clairement l'intention qui devrait être publié le blogpost.

De même, les setters peuvent casser les invariants d'objet. Par exemple, si nous avons une entité "Triangle", l'invariant devrait être que la somme de tous les angles devrait être de 180 degrés.

Assert.AreEqual (t.A + t.B + t.C ,180); 

Maintenant, si nous avons setters, nous pouvons facilement briser les invariants:

t.A = 0; 
t.B = 0; 
t.C = 0; 

Nous avons donc un triangle où la somme de tous les angles sont égaux à 0 ... Est-ce vraiment un triangle? Je dirais non.

setters avec des méthodes pourraient remplacement nous forcer à maintenir invariants:

t.SetAngles(0,0,0); //should fail 

Un tel appel doit lancer une exception vous dire que cela provoque un état non valide pour votre entité.

Vous obtenez donc la sémantique et les invariants avec des méthodes au lieu de setters.

+0

tant que je ne casse pas un invariant en utilisant setters est OK? Je veux dire si un utilisateur édite une information client (Prénom, Deuxième prénom, Nom) et le seul invariant est qu'aucun d'entre eux ne peut être vide ou NULL alors l'utilisation d'un setter pour chaque champ est acceptable? – Songo

-1

Je suis peut-être ici, mais je pense que les setters devraient être utilisés pour définir, par opposition aux méthodes de setter. J'ai quelques raisons à cela. A) Cela a du sens dans .Net. Chaque développeur connaît les propriétés. Voilà comment vous définissez les choses sur un objet. Pourquoi s'écarter de cela pour les objets de domaine?

b) Les setters peuvent avoir du code. Avant 3.5 je crois, la fixation d'un objet composé d'une variable interne et une signature de propriété

private object _someProperty = null; 
public object SomeProperty{ 
    get { return _someProperty; } 
    set { _someProperty = value; } 
} 

Il est très facile et élégante de mettre imo la validation du compositeur. En IL, les getters et setters sont de toute façon convertis en méthodes. Pourquoi dupliquer le code?

Dans l'exemple de la méthode Publish() affiché ci-dessus, je suis entièrement d'accord. Il y a des moments où nous ne voulons pas que d'autres développeurs définissent une propriété. Cela devrait être géré par une méthode. Cependant, est-il sensé d'avoir une méthode setter pour chaque propriété quand .Net fournit déjà toutes les fonctionnalités dont nous avons besoin dans la déclaration de propriété?

Si vous avez un objet Personne, pourquoi créer des méthodes pour chaque propriété s'il n'y a pas de raison?