38 votes

Comment résoudre les violations de la loi de Déméter?

Un collègue et j'ai conçu un système pour nos clients, et à notre avis, nous avons créé un design agréable et très propre. Mais je vais avoir des problèmes avec certains de couplage que nous avons introduit. Je pourrais essayer de créer un exemple de conception qui comprend les mêmes problèmes que notre conception, mais si vous ne pardonnez moi, je vais créer un extrait de notre conception à l'appui de la question.

Nous sommes en train de développer un système pour l'enregistrement de certains traitements pour les patients. Pour éviter d'avoir un lien rompu à l'image que je vais décrire les concepts UML diagramme de classes c# style de la définition de la classe.

class Discipline {}
class ProtocolKind 
{ 
   Discipline; 
}
class Protocol
{
   ProtocolKind;
   ProtocolMedication; //1..*
}
class ProtocolMedication
{
   Medicine;
}
class Medicine
{
   AdministrationRoute;
}
class AdministrationRoute {}

Je vais essayer d'expliquer un peu plus sur le design, un protocole est le modèle pour un nouveau traitement. Et un protocole est d'un certain Type et a des médicaments qui doivent être administrés. Selon le protocole, le dosage peut varier pour le même médicament (entre autres choses), donc c'est stockée dans le ProtocolMedication classe. AdministrationRoute est la manière dont le médicament est administré et iscreated/mis à jour indépendamment de la gestion du protocole.

J'ai trouvé les endroits suivants que nous allons avoir une violation de la Loi de Déméter:

Les Violations de la Loi de Déméter

À l'intérieur de la BLL

Par exemple, à l'intérieur de la logique métier de l'ProtocolMedication il y a des règles qui dépendent de la AdministrationRoute.Soluble propriété de la médecine. Le code pourrait devenir

if (!Medicine.AdministrationRoute.Soluble)
{
   //validate constrains on fields
}

À l'intérieur de l'dépôts

Une méthode qui serait la liste de tous les protocoles dans une certaine Discipline devrait être écrite comme:

public IQueryable<Protocol> ListQueryable(Discipline discipline)
{
    return ListQueryable().Where(p => (p.Kind.Discipline.Id == discipline.Id)); // Entity Frameworks needs you to compare the Id...
}

À l'intérieur de l'Interface Utilisateur

Nous utilisons ASP.NET (pas de MVC) pour l'interface de notre système, à mon avis, cette couche a actuellement les pires violations. Le databinding d'un gridview (une colonne qui doit afficher la Discipline d'un protocole doit se lier à la Nature.De la Discipline.Nom), qui sont des chaînes de caractères, donc pas le temps de compilation des erreurs.

<asp:TemplateField HeaderText="Discipline" SortExpression="Kind.Discipline.Name">
   <ItemTemplate>
      <%# Eval("Kind.Discipline.Name")%>
   </ItemTemplate>
</asp:TemplateField>

Donc, je pense que la véritable question qui est peut-être, quand serait-il bon de regarder cela de plus que la Suggestion de Déméter, et ce qui peut être fait pour résoudre les violations de la Loi de Déméter?

J'ai un peu l'idée de moi-même, mais je vais les poster que des réponses de sorte qu'ils peuvent être commentés et voté separemment. (Je ne suis pas sûr que c'est la façon de le faire, si non, je vais supprimer mes réponses et de les ajouter à la question).

32voto

Pete Kirkham Points 32484

Ma compréhension des conséquences de la Loi de Déméter semble être différent de DrJokepu - à chaque fois que j'ai appliqué le code orienté objet, il est traduite par un resserrement de l'encapsulation et de la cohésion, plutôt que de l'ajout de méthodes de contrat chemins d'accès dans le code de procédure.

Wikipedia a la règle

Plus formellement, la Loi de Déméter pour fonctions nécessite qu'une méthode M de un objet O ne peut invoquer l' méthodes des types suivants de objets:

  1. O lui-même
  2. M paramètres
  3. les objets créés/instancié dans M
  4. O la composante directe des objets

Si vous avez une méthode qui prend "cuisine" en tant que paramètre, Déméter dit que vous ne pouvez inspecter les composants de la cuisine, non pas que vous ne pouvez inspecter l'immédiat composants.

L'écriture d'un tas de fonctions pour satisfaire la Loi de Déméter comme ceci

Kitchen.GetCeilingColour()

ça ressemble juste à une totale perte de temps pour moi et effectivement, c'est ma façon de faire les choses

Si une méthode en dehors de la Cuisine est passé d'une cuisine, d'une stricte Déméter il ne peut pas appeler une méthode sur le résultat de GetCeilingColour() dessus.

Mais de toute façon, l'important est de supprimer la dépendance à l'égard de la structure plutôt que de déplacer la représentation de la structure à partir d'une séquence de enchaînés méthodes pour le nom de la méthode. Des méthodes telles que MoveTheLeftHindLegForward() dans une classe Chien ne veut pas faire quelque chose vers la réalisation de Déméter. Au lieu de cela, appelez chien.marche() et laisser le chien gérer ses propres jambes.

Par exemple, si les besoins changent et j'aurai besoin de la hauteur sous plafond est trop?

J'avais refactoriser le code de sorte que vous travaillez avec de la place et plafonds:

interface RoomVisitor {
  void visitFloor (Floor floor) ...
  void visitCeiling (Ceiling ceiling) ...
  void visitWall (Wall wall ...
}

interface Room { accept (RoomVisitor visitor) ; }

Kitchen.accept(RoomVisitor visitor) {
   visitor.visitCeiling(this.ceiling);
   ...
}

Ou vous pouvez aller plus loin et d'éliminer les getters totalement en passant les paramètres du plafond de la visitCeiling méthode, mais qui introduit souvent un fragile de couplage.

En l'appliquant à la médicaux exemple, je m'attends à une SolubleAdminstrationRoute pour être en mesure de valider la médecine, ou au moins appeler la médecine du validateForSolubleAdministration méthode si il y a des données encapsulées dans le médicament de la classe qui est requis pour la validation.

Cependant, Déméter s'applique à OO des systèmes où les données sont encapsulées dans des objets qui fonctionnent sur les données plutôt que le système dont vous parlez, qui a différentes couches d'une transmission des données entre les couches de muet, navigatable structures. Je ne vois pas ce que Déméter peut nécessairement être appliquée à de tels systèmes, aussi facilement que pour monolithique ou d'un message basé. (Dans un message basé sur le système, vous ne pouvez pas naviguer à tout ce qui n'est pas dans l'grammes de message, donc vous êtes coincé avec Déméter si vous l'aimez ou pas)

23voto

Tamas Czinege Points 49277

Je sais que je vais avoir downvoted de total annihilation, mais je dois dire que je sorte de dégoût de la Loi de Déméter. Certes, des choses comme

dictionary["somekey"].headers[1].references[2]

sont vraiment laid, mais réfléchissez à ceci:

Kitchen.Ceiling.Coulour

Je n'ai rien contre ce. L'écriture d'un tas de fonctions pour satisfaire la Loi de Déméter comme ceci

Kitchen.GetCeilingColour()

ça ressemble juste à une totale perte de temps pour moi et effectivement, c'est ma façon de faire les choses. Par exemple, si les besoins changent et j'aurai besoin de la hauteur sous plafond est trop? Avec la Loi de Déméter, je dois écrire une autre fonction dans la Cuisine afin que je puisse obtenir la hauteur de Plafond directement, et à la fin je vais avoir un tas de petites fonctions de lecture partout qui est quelque chose que je considère comme tout à fait quelques dégâts.

EDIT: Permettez-moi de reformuler mon propos:

Est ce niveau d'abstraction des choses tellement important que je vais passer du temps sur l'écriture de 3-4-5 niveaux de getters/setters? Est-il vraiment de rendre la maintenance plus facile? L'utilisateur final gagner quoi que ce soit? Est-il utile de mon temps? Je ne le pense pas.

12voto

kdgregory Points 21849

La solution traditionnelle à Déméter violations est "dire, ne demandez pas." En d'autres mots, en fonction de votre état, vous devriez dire à un objet géré (n'importe quel objet que vous tenez) de prendre des mesures -- et il va décider de faire ce que vous demandez, en fonction de son propre état.

Un exemple simple: mon code utilise une structure de journalisation, et je dis à mon enregistreur que je veux en sortie un message de débogage. L'enregistreur décide, en fonction de sa configuration (peut-être le débogage n'est pas activé) si ou de ne pas envoyer le message à ses périphériques de sortie. Un LoD violation dans ce cas serait pour mon objet de demander à l'enregistreur de savoir si il va faire n'importe quoi avec le message. En faisant ainsi, j'ai couplé mon code à la connaissance de l'enregistreur de données interne de l'état (et oui, j'ai pris cet exemple volontairement).

Toutefois, le point essentiel de cet exemple est que l'exploitant met en œuvre le comportement.

Là où je pense que le LoD rupture lorsque vous traitez avec un objet qui représente des données, avec aucun problème.

Dans ce cas, l'OMI de la traversée de l'objet graphique n'est pas différent que d'appliquer une expression XPath pour un DOM. Et l'ajout de méthodes telles que "isThisMedicationWarranted()" est un pire approche, parce que maintenant, vous êtes à la distribution des règles entre les objets, les rendant plus difficiles à comprendre.

4voto

goku_da_master Points 903

J'ai eu du mal avec le LoD comme beaucoup d'entre vous étaient jusqu'à ce que j'ai regardé Le Code de Nettoyage Parle" session intitulée:

"Ne Cherchez pas les Choses"

La vidéo vous permet d'utiliser l'Injection de Dépendance de mieux, qui, par nature, ne peut régler les problèmes avec LoD. En changeant votre conception un peu, vous pouvez passer à beaucoup plus faible niveau des objets ou des sous-types lors de la construction d'un objet parent, ce qui empêche les parents d'avoir à marcher sur la dépendance de la chaîne à travers les objets enfants.

Dans votre exemple, vous avez besoin de passer en AdministrationRoute le constructeur de ProtocolMedication. Vous avez de la refonte de quelques choses de sorte qu'il fait sens, mais c'est l'idée.

Cela dit, étant nouveau pour le LoD et pas expert, j'aurais tendance à être d'accord avec vous et DrJokepu. Il y a probablement des exceptions à la LoD comme la plupart des règles, et il ne pourrait pas s'appliquer à votre conception.

[ A quelques années de retard, je sais que cette réponse ne sera probablement pas aider à l'auteur, mais ce n'est pas pourquoi je poste cette ]

2voto

Arkadiy Points 10567

Je dois supposer que la logique métier qui requiert Soluble requiert également d'autres choses. Dans l'affirmative, une partie de celle-ci peut-elle être incapsulée en médecine de manière significative (plus significative que Medicine.isSoluble ())?

Une autre possibilité (probablement une solution exagérée et non complète en même temps) serait de présenter la règle métier comme son propre objet et d'utiliser un modèle de double répartition / visiteur:

 RuleCompilator
{
  lookAt(Protocol);
  lookAt(Medicine);
  lookAt(AdminstrationProcedure) 
}

MyComplexRuleCompilator : RuleCompilator
{
  lookaAt(Protocol)
  lookAt(AdminstrationProcedure)
}

Medicine
{
  applyRuleCompilator(RuleCompilator c) {
    c.lookAt(this);
    AdministrationProtocol.applyRuleCompilator(c);
  }
}
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X