Dans la conception de mon programme, j'arrive souvent au point où je dois passer des instances d'objet à travers plusieurs classes. Par exemple, si j'ai un contrôleur qui charge un fichier audio, puis le transmet à un lecteur, et le lecteur le transmet au playerRunnable, qui le transmet à nouveau ailleurs, etc. Il a l'air plutôt mauvais, mais je ne savoir comment l'éviter. Ou est-ce OK de le faire?
EDIT: Peut-être que l'exemple du lecteur n'est pas le meilleur car je pourrais charger le fichier plus tard, mais dans d'autres cas cela ne fonctionne pas.
Comme d'autres l'ont mentionné, ce n'est pas nécessairement une mauvaise pratique, mais vous devez faire attention à ne pas rompre la séparation des préoccupations des couches et à passer des instances spécifiques aux couches entre les couches. Par exemple:
Cependant, si les instances que vous passez sont les DTO ou les entités elles-mêmes, c'est probablement correct.
Intéressant dont personne n'a parlé Objets immuables pour l'instant. Je dirais que faire passer un objet immuable à travers toutes les différentes couches est en fait une bonne chose plutôt que de créer beaucoup d'objets de courte durée de vie pour chaque couche.
Il y a de grandes discussions sur l'immuabilité par Eric Lippert sur son blog
D'un autre côté, je dirais que le passage d'objets mutables entre les couches est une mauvaise conception . Vous construisez essentiellement une couche avec la promesse que les couches environnantes ne la modifieront pas de manière à casser votre code.
Passer des instances d'objets est une chose normale à faire. Il réduit le besoin de conserver l'état (c'est-à-dire les variables d'instance) et dissocie le code de son contexte d'exécution.
Un problème auquel vous pouvez être confronté est la refactorisation, lorsque vous devez modifier les signatures de plusieurs méthodes le long de la chaîne d'invocation en réponse aux exigences de paramètres changeantes d'une méthode proche du bas de cette chaîne. Cependant, il peut être atténué par l'utilisation d'outils de développement de logiciels modernes qui aident à la refactorisation.
Peut-être mineur, mais il y a le risque d'attribuer cette référence quelque part dans l'une des couches, ce qui pourrait entraîner une référence pendante ou une fuite de mémoire ultérieurement.
Si vous faites passer des objets simplement parce que cela est nécessaire dans une zone éloignée de votre code, l'utilisation des modèles de conception d'injection de dépendance inversion de contrôle et éventuellement d'un conteneur IoC approprié peut très bien résoudre les problèmes de transport d'instances d'objet environ. Je l'ai utilisé sur un projet de taille moyenne et je n'envisagerais plus jamais d'écrire un gros morceau de code serveur sans l'utiliser.
Passer des données à travers un tas de couches n'est pas une mauvaise chose, c'est vraiment la seule façon dont un système en couches peut fonctionner sans violer la structure en couches. Le signe qu'il y a des problèmes est lorsque vous transmettez vos données à plusieurs objets dans la même couche pour atteindre votre objectif.
Réponse rapide: Il n'y a rien de mal lors du passage d'instances d'objets. Comme également mentionné, il est important de sauter l'attribution de cette référence dans toutes les couches pouvant provoquer une référence pendante ou des fuites de mémoire.
Dans nos projets, nous utilisons cette pratique pour passer DTO (objet de transfert de données) entre les couches et c'est une pratique très utile . Nous réutilisons également nos objets dto pour construire une fois plus complexe, comme pour des informations résumées.
Je suis principalement un développeur de l'interface utilisateur Web, mais il me semble que votre inconfort intuitif pourrait être moins lié à la transmission d'instance et plus au fait que vous allez un peu procédural avec ce contrôleur. Votre manette devrait-elle transpirer tous ces détails? Pourquoi fait-il même référence à plus d'un autre nom d'objet pour la lecture audio?
Dans la conception OOP, j'ai tendance à penser en fonction de ce qui est persistant et de ce qui est le plus susceptible d'être sujet à changement. Le sujet à changer est ce que vous voudrez avoir tendance à mettre vos plus grandes boîtes d'objets afin que vous puissiez conserver des interfaces cohérentes même lorsque les joueurs changent ou que de nouvelles options sont ajoutées. Ou vous vous retrouvez à vouloir échanger des objets audio ou des composants en gros.
Dans ce cas, votre contrôleur doit identifier qu'il est nécessaire de lire un fichier audio, puis disposer d'un moyen cohérent/permanent de le faire jouer. D'autre part, le lecteur audio pourrait facilement changer à mesure que la technologie et les plates-formes sont modifiées ou que de nouveaux choix sont ajoutés. Tous ces détails devraient se trouver sous l'interface d'un objet composite plus grand, IMO, et vous ne devriez pas avoir à réécrire votre contrôleur lorsque les détails de la lecture de l'audio changent. Ensuite, lorsque vous passez une instance d'objet avec des détails tels que l'emplacement du fichier dans l'objet plus grand, tout ce changement est effectué à l'intérieur d'un contexte approprié où quelqu'un est moins susceptible de faire quelque chose de stupide avec.
Donc, dans ce cas, je ne pense pas que ce soit l'instance d'objet qui pourrait être perturbée. C'est que le capitaine Picard descend dans la salle des machines pour allumer le noyau de la chaîne, remontant vers le pont pour tracer les coordonnées, puis appuyant sur le bouton "punch-it" après avoir allumé les boucliers plutôt que de simplement dire "Prendre nous à la planète X à Warp 9. Faites en sorte. " et laisser son équipage régler les détails. Parce que quand il le gère de cette façon, il peut commander n'importe quel navire de la flotte sans connaître la disposition de chaque navire et comment tout fonctionne. Et c'est finalement la plus grande victoire de conception OOP pour laquelle tirer, IMO.
C'est une conception assez courante qui finit par se produire bien que vous puissiez avoir des problèmes de latence si votre application est sensible à ce genre de chose.
Ce problème peut être résolu avec des variables de portée dynamique, si votre langue les possède, ou avec un stockage local de thread. Ces mécanismes nous permettent d'associer certaines variables personnalisées à une chaîne d'activation ou à un thread de contrôle, afin que nous n'ayons pas à transmettre ces valeurs dans un code qui n'a rien à voir avec elles juste pour qu'elles puissent être communiquées à un autre code qui en a besoin.
Comme les autres réponses l'ont souligné, il ne s'agit pas d'une conception intrinsèquement médiocre. Il peut créer un couplage étroit entre les classes imbriquées et celles qui les imbriquent, mais desserrer le couplage peut ne pas être une option valide si l'imbrication des références fournit une valeur à la conception.
Une solution possible consiste à "aplatir" les références imbriquées dans la classe de contrôleur.
Au lieu de passer plusieurs fois un paramètre à travers des objets imbriqués, vous pouvez conserver dans la classe de contrôleur des références à tous les objets imbriqués.
La manière exacte dont cela est mis en œuvre (ou s'il s'agit même d'une solution valide) dépend de la conception actuelle du système, par exemple:
C'est un problème que j'ai rencontré dans un modèle de conception MVC pour un client GXT. Nos composants GUI contenaient des composants GUI imbriqués pour plusieurs couches. Lorsque les données du modèle ont été mises à jour, nous avons fini par les passer à travers les différentes couches jusqu'à ce qu'elles atteignent le ou les composants appropriés. Cela a créé un couplage indésirable entre les composants GUI car si nous voulions qu'une nouvelle classe de composants GUI accepte les données du modèle, nous devions créer des méthodes pour mettre à jour les données du modèle dans tous les composants GUI qui contenaient la nouvelle classe.
Pour y remédier, nous avons conservé dans la classe View une carte de références à tous les composants GUI imbriqués afin que chaque fois que les données du modèle soient mises à jour, View puisse envoyer les données du modèle mises à jour directement aux composants GUI qui en avaient besoin, fin de l'histoire . Cela a bien fonctionné car il n'y avait qu'une seule instance de chaque composant GUI. Je pouvais voir que cela ne fonctionnait pas si bien s'il y avait plusieurs instances de certains composants de l'interface graphique, ce qui rend difficile l'identification de la copie à mettre à jour.
Ce que vous décrivez est un modèle de conception appelé Chaîne de responsabilité . Apple utilise ce modèle pour leur système de gestion d'événements, pour ce qu'il vaut.