En considérant un logiciel de taille moyenne à grande avec une architecture à n couches et une injection de dépendances, je suis à l'aise de dire qu'un objet appartenant à une couche peut dépendre d'objets de couches inférieures mais jamais d'objets de couches supérieures.
Mais je ne sais pas quoi penser des objets qui dépendent d'autres objets du même calque.
À titre d'exemple, supposons une application avec trois couches et plusieurs objets comme celui de l'image. Évidemment, les dépendances de haut en bas (flèches vertes) sont correctes, de bas en haut (flèche rouge) ne le sont pas, mais qu'en est-il d'une dépendance à l'intérieur du même calque (flèche jaune)?
À l'exclusion de la dépendance circulaire, je suis curieux de savoir tout autre problème qui pourrait survenir et à quel point l'architecture en couches est violée dans ce cas.
la source
Réponses:
Oui, les objets d'une couche peuvent avoir des dépendances directes entre elles, parfois même cycliques - c'est en fait ce qui fait la différence de base avec les dépendances autorisées entre les objets de différentes couches, où aucune dépendance directe n'est autorisée, ou juste une dépendance stricte direction .
Cependant, cela ne signifie pas qu'ils devraient avoir de telles dépendances de manière arbitraire. Cela dépend en fait de ce que représentent vos couches, de la taille du système et de la responsabilité des pièces. Notez que «architecture en couches» est un terme vague, il y a une énorme variation de ce que cela signifie réellement dans différents types de systèmes.
Par exemple, supposons que vous ayez un "système à couches horizontales", avec une couche de base de données, une couche métier et une couche d'interface utilisateur (UI). Disons que la couche d'interface utilisateur contient au moins plusieurs dizaines de classes de dialogue différentes.
On peut choisir une conception où aucune des classes de dialogue ne dépend directement d'une autre classe de dialogue. On peut choisir une conception où les "dialogues principaux" et les "dialogues secondaires" existent et il n'y a que des dépendances directes des dialogues "principaux" à "secondaires". Ou on peut préférer une conception où n'importe quelle classe d'interface utilisateur existante peut utiliser / réutiliser toute autre classe d'interface utilisateur de la même couche.
Ce sont tous des choix de conception possibles, peut-être plus ou moins judicieux selon le type de système que vous construisez, mais aucun d'entre eux ne rend la "superposition" de votre système invalide.
la source
Pour être honnête, je ne pense pas que vous devriez être à l'aise avec cela. Lorsque je traite avec autre chose qu'un système trivial, je viserais à ce que toutes les couches ne dépendent que des abstractions des autres couches; à la fois inférieur et supérieur.
Ainsi, par exemple,
Obj 1
ne devrait pas dépendreObj 3
. Il doit avoir une dépendance par exempleIObj 3
et doit être informé de l'implémentation de cette abstraction avec laquelle il doit travailler lors de l'exécution. La chose faisant le récit ne devrait être liée à aucun des niveaux, car il est de mapper ces dépendances. Cela pourrait être un conteneur IoC, un code personnalisé appelé par exemple parmain
qui utilise la DI pure. Ou sur simple pression, il pourrait même s'agir d'un localisateur de services. Quoi qu'il en soit, les dépendances n'existent pas entre les couches jusqu'à ce que cette chose fournisse le mappage.Je dirais que c'est la seule fois où vous devriez avoir des dépendances directes. Cela fait partie du fonctionnement interne de cette couche et peut être modifié sans affecter les autres couches. Ce n'est donc pas un couplage nuisible.
la source
Regardons cela pratiquement
Obj 3
sait maintenantObj 4
existe. Et alors? Pourquoi on s'en soucie?D'accord, mais tous les objets ne sont-ils pas des abstractions?
OK mais, si mon objet est correctement encapsulé, cela ne cache-t-il aucun détail?
Certaines personnes insistent aveuglément sur le fait que chaque objet a besoin d'une interface de mot-clé. Je n'en fais pas partie. J'aime insister aveuglément sur le fait que si vous ne les utilisez pas maintenant, vous avez besoin d'un plan pour gérer plus tard quelque chose comme eux.
Si votre code est entièrement refactorable sur chaque version, vous pouvez simplement extraire les interfaces plus tard si vous en avez besoin. Si vous avez publié du code que vous ne voulez pas recompiler et que vous souhaitez que vous parliez via une interface, vous aurez besoin d'un plan.
Obj 3
saitObj 4
existe. MaisObj 3
sait si leObj 4
béton est?C'est ici pourquoi il est si agréable de NE PAS se propager
new
partout. SiObj 3
je ne sais pas s'ilObj 4
est concret, probablement parce qu'il ne l'a pas créé, alors si vous vous glissez plus tard et que vous vous transformezObj 4
en classe abstraite,Obj 3
cela ne vous importera pas.Si vous pouvez le faire, cela
Obj 4
a toujours été entièrement abstrait. La seule chose qui crée une interface entre eux dès le départ, c'est l'assurance que quelqu'un n'ajoutera pas accidentellement du code qui donne duObj 4
concret en ce moment. Les constructeurs protégés peuvent atténuer ce risque mais cela conduit à une autre question:Obj 3 et obj 4 sont-ils dans le même package?
Les objets sont souvent regroupés d'une manière ou d'une autre (package, espace de noms, etc.). Lorsqu'ils sont regroupés judicieusement, les impacts plus probables au sein d'un groupe plutôt qu'entre les groupes changent.
J'aime grouper par fonctionnalité. Si
Obj 3
etObj 4
sont dans le même groupe et la même couche, il est très peu probable que vous en ayez publié un et que vous ne souhaitiez pas le refactoriser tout en n'ayant besoin de changer que l'autre. Cela signifie que ces objets sont moins susceptibles de bénéficier d'une abstraction entre eux avant d'avoir un besoin clair.Si vous traversez une limite de groupe, c'est vraiment une bonne idée de laisser les objets de chaque côté varier indépendamment.
Cela devrait être aussi simple, mais malheureusement, Java et C # ont fait des choix malheureux qui compliquent cela.
En C #, il est de tradition de nommer chaque interface de mot-clé avec un
I
préfixe. Cela oblige les clients à SAVOIR qu'ils parlent à une interface de mots clés. Cela perturbe le plan de refactoring.En Java, il est de tradition d'utiliser un meilleur modèle de dénomination:
FooImple implements Foo
Cependant, cela n'aide qu'au niveau du code source car Java compile des interfaces de mots clés vers un autre binaire. Cela signifie que lorsque vous refactorisezFoo
des clients concrets en clients abstraits qui n'ont pas besoin d'un seul caractère de code modifié, vous devez encore les recompiler.Ce sont ces BOGUES dans ces langages particuliers qui empêchent les gens de repousser l'abstrait formel jusqu'à ce qu'ils en aient vraiment besoin. Vous n'avez pas dit quelle langue vous utilisez mais comprenez qu'il y a des langues qui n'ont tout simplement pas ces problèmes.
Vous n'avez pas dit la langue que vous utilisez, je vous invite donc à analyser attentivement votre langue et votre situation avant de décider que ce seront des interfaces de mots clés partout.
Le principe YAGNI joue ici un rôle clé. Mais il en va de même "S'il vous plaît, rendez-vous difficile de me tirer dans le pied".
la source
En plus des réponses ci-dessus, je pense que cela pourrait vous aider à le regarder sous différents points de vue.
Par exemple, du point de vue de la règle de dépendance . DR est une règle suggérée par Robert C. Martin pour sa célèbre architecture propre .
Ça dit
Par politiques de niveau supérieur , il entend un niveau d'abstractions de niveau supérieur . Composants qui fuient sur les détails d'implémentation, comme par exemple les interfaces ou les classes abstraites contrairement aux classes concrètes ou aux structures de données.
Le fait est que la règle n'est pas limitée à la dépendance inter-couches. Il souligne uniquement la dépendance entre les morceaux de code, quel que soit l'emplacement ou la couche à laquelle ils appartiennent.
Donc non, il n'y a rien de mal à avoir des dépendances entre les éléments d'une même couche. Cependant, la dépendance peut toujours être implémentée pour transmettre avec le principe de dépendance stable .
Un autre point de vue est SRP.
Le découplage est notre façon de briser les dépendances nuisibles et de transmettre avec certaines des meilleures pratiques comme l'inversion de dépendance (IoC). Cependant, les éléments qui partagent les raisons de changer ne donnent pas de raisons de découplage car les éléments ayant la même raison de changer changeront en même temps (très probablement) et ils seront déployés en même temps aussi. Si c'est le cas entre
Obj3
etObj4
puis, encore une fois, il n'y a rien de mal en soi.la source