Est-il problématique d'avoir une dépendance entre des objets de la même couche dans une architecture logicielle en couches?

12

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)?

entrez la description de l'image ici

À 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.

bracco23
la source
Si vous n'avez pas de cycles, dans une couche où vous avez les liens horizontaux, il existe une division en sous-couches qui n'a que des dépendances entre les couches
max630

Réponses:

10

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.

Doc Brown
la source
En continuant sur l'exemple de l'interface utilisateur, quels sont les avantages et les inconvénients d'avoir les dépendances internes? Je peux voir que cela facilite la réutilisation et introduit des dépendances cycliques, ce qui pourrait être un problème selon la méthode DI utilisée. Rien d'autre?
bracco23
@ bracco23: les avantages et les inconvénients d'avoir des dépendances "internes" sont les mêmes que les avantages des inconvénients d'avoir des dépendances arbitraires. Les «inconvénients» sont qu'ils rendent les composants plus difficiles à réutiliser et plus difficiles à tester, en particulier isolés des autres composants. Les avantages sont que les dépendances explicites rendent les choses qui nécessitent de la cohésion plus faciles à utiliser, à comprendre, à gérer et à tester.
Doc Brown
14

Je suis à l'aise de dire qu'un objet appartenant à un calque peut dépendre des objets des calques inférieurs

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 1ne devrait pas dépendre Obj 3. Il doit avoir une dépendance par exemple IObj 3et 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 par mainqui 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.

Mais je ne sais pas quoi penser des objets qui dépendent d'autres objets du même calque.

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.

David Arno
la source
Merci d'avoir répondu. Oui, le calque ne doit pas exposer des objets mais des interfaces, je le sais mais je l'ai laissé de côté pour plus de simplicité.
bracco23
4

Regardons cela pratiquement

entrez la description de l'image ici

Obj 3sait maintenant Obj 4existe. Et alors? Pourquoi on s'en soucie?

DIP dit

"Les modules de haut niveau ne devraient pas dépendre de modules de bas niveau. Les deux devraient dépendre d'abstractions."

D'accord, mais tous les objets ne sont-ils pas des abstractions?

DIP dit aussi

"Les abstractions ne doivent pas dépendre des détails. Les détails doivent dépendre 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 3sait Obj 4existe. Mais Obj 3sait si le Obj 4béton est?

C'est ici pourquoi il est si agréable de NE PAS se propager newpartout. Si Obj 3je ne sais pas s'il Obj 4est concret, probablement parce qu'il ne l'a pas créé, alors si vous vous glissez plus tard et que vous vous transformez Obj 4en classe abstraite, Obj 3cela ne vous importera pas.

Si vous pouvez le faire, cela Obj 4a 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 du Obj 4concret 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 3et Obj 4sont 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 Ipré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 FooCependant, 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 refactorisez Foodes 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".

candied_orange
la source
0

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

Les dépendances du code source doivent pointer uniquement vers l'intérieur, vers des stratégies de niveau supérieur.

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 Obj3et Obj4puis, encore une fois, il n'y a rien de mal en soi.

Laiv
la source