Regardons cela pratiquement
Obj 3
sait maintenant Obj 4
existe. 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 3
sait Obj 4
existe. Mais Obj 3
sait si le Obj 4
béton est?
C'est ici pourquoi il est si agréable de NE PAS se propager new
partout. Si Obj 3
je ne sais pas s'il Obj 4
est concret, probablement parce qu'il ne l'a pas créé, alors si vous vous glissez plus tard et que vous vous transformez Obj 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 du Obj 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
et Obj 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 refactorisez Foo
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".