Est-ce une odeur de code si un objet connaît beaucoup de son propriétaire?

9

Dans notre application Delphi 2007, nous utilisons un grand nombre des constructions suivantes

FdmBasic:=TdmBasicData(FindOwnerClass(AOwner,TdmBasicData));

FindOwnerClass parcourt la hiérarchie Propriétaire du composant actuel vers le haut pour rechercher une classe spécifique (dans l'exemple TdmBasicData). L'objet résultant est stocké dans la variable de champ FdmBasic. Nous l'utilisons principalement pour transmettre des modules de données.

Exemple: lors de la génération d'un rapport, les données résultantes sont compressées et stockées dans un champ Blob d'une table accessible via un module de données TdmReportBaseData. Dans un module distinct de notre application, il existe une fonctionnalité pour afficher les données du rapport sous une forme paginée à l'aide de ReportBuilder. Le code principal de ce module (TdmRBReport), utilise une classe TRBTempdatabase pour convertir les données de blob compressées en différentes tables qui sont utilisables dans le Reportdesigner d'exécution Reportdesigner. TdmRBReport a accès à TdmReportBaseData pour toutes sortes de données liées au rapport (type de rapport, paramètres de calcul de rapport, etc.). TRBTempDatabase est construit dans TdmRBReport mais doit avoir accès à TdmReportBasedata. Donc, cela se fait maintenant en utilisant la construction ci-dessus:

constructor TRBTempDatabase.Create(aOwner: TComponent);
begin
  inherited Create(aOwner);

  FdmReportBaseData := TdmRBReport(FindOwnerClass(Owner, TdmRBReport)).dmReportBaseData;
end;{- .Create }

Mon sentiment est que cela signifie que TRBTempDatabase connaît beaucoup de son propriétaire, et je me demandais s'il s'agissait d'une sorte d'odeur de code ou d'Anti-pattern.

Qu'en pensez-vous? Est-ce une odeur de code? Si oui, quelle est la meilleure façon?

Bascy
la source
1
S'il était censé en savoir autant sur une autre classe, il aurait été plus facile de le faire.
Loren Pechtel

Réponses:

13

Ce type ressemble à un modèle de localisateur de service qui a été décrit pour la première fois par Martin Fowler (qui a été identifié comme un anti-modèle commun).

L'injection de dépendances basée sur la construction est préférée à un localisateur de services car elle favorise la visibilité des paramètres requis et facilite les tests unitaires plus simples.

Le problème avec l'utilisation d'un localisateur de service n'est pas que vous dépendez d'une implémentation particulière du localisateur de service (bien que cela puisse également être un problème), mais qu'il s'agit d'un anti-modèle authentique. Cela offrira aux consommateurs de votre API une horrible expérience de développeur et rendra votre vie en tant que développeur de maintenance pire, car vous devrez utiliser des quantités considérables de puissance cérébrale pour comprendre les implications de chaque changement que vous apportez.

Le compilateur peut offrir aux consommateurs et aux producteurs autant d'aide lorsque l'injection de constructeur est utilisée, mais aucune de cette assistance n'est disponible pour les API qui reposent sur Service Locator.

De plus, il enfreint également la loi de Déméter

La loi de Demeter (LoD) ou principe de moindre connaissance est une directive de conception pour le développement de logiciels, en particulier les programmes orientés objet. Dans sa forme générale, le LoD est un cas spécifique de couplage lâche.

La loi de Déméter pour les fonctions exige qu'une méthode M d'un objet O ne puisse invoquer que les méthodes des types d'objets suivants:

  1. O lui-même
  2. Paramètres de M
  3. tout objet créé / instancié dans M
  4. Objets composants directs d'O
  5. une variable globale, accessible par O, dans le cadre de M

En particulier, un objet doit éviter d'appeler des méthodes d'un objet membre renvoyé par une autre méthode. Pour de nombreux langages orientés objet modernes qui utilisent un point comme identifiant de champ, la loi peut être simplement déclarée "n'utiliser qu'un seul point". Autrement dit, le code abMethod () enfreint la loi là où a.Method () ne le fait pas. À titre d'exemple simple, quand on veut promener un chien, ce serait une folie de commander aux jambes du chien de marcher directement; au lieu de cela, on commande le chien et on le laisse prendre soin de ses propres pattes.

La meilleure façon

En fait, la meilleure façon est de supprimer l'appel du localisateur de service au sein de la classe et de passer le bon propriétaire en tant que paramètre à l'intérieur de son constructeur. Même si cela signifie que vous avez une classe de service qui effectue une recherche de propriétaire puis la transmet au constructeur de classe

constructor TRBTempDatabase.Create(aOwner: TComponent, ownerClass: IComponent);
begin
  inherited Create(aOwner);

  FdmReportBaseData := TdmRBReport(ownerClass).dmReportBaseData;
end;{- .Create }
Justin Shield
la source
3
Excellente réponse et accessoires à quiconque a proposé cette merveilleuse analogie:As a simple example, when one wants to walk a dog, it would be folly to command the dog's legs to walk directly; instead one commands the dog and lets it take care of its own legs.
Andy Hunt
3

L'une des difficultés avec les objets enfants en sachant trop sur le parent est que vous finissez par mettre en œuvre des modèles qui peuvent (et le plus souvent) devenir trop étroitement couplés, ce qui crée des maux de tête de dépendance majeurs et devient souvent très difficile à modifier et à maintenir en toute sécurité plus tard. plus tard.

Selon la profondeur de connexion de vos deux classes, cela ressemble un peu à la description de Fowler de la fonctionnalité Envie ou des odeurs de code d'intimité inappropriées.

Il semble qu'il soit nécessaire de charger ou de lire une classe avec des données, auquel cas vous pouvez utiliser un certain nombre de modèles alternatifs pour briser la dépendance entre l'enfant et sa chaîne de parents, et il semble que vous devez déléguer la tâche d'accéder à votre classe de données plutôt que de rendre la classe d'accesseur de données responsable de tout faire elle-même.

S.Robins
la source