Tout d'abord, comprenez qu'une classe purement abstraite n'est vraiment qu'une interface qui ne peut pas faire d'héritage multiple.
Écrire une classe, extraire l'interface, est une activité morte du cerveau. Tant et si bien que nous avons un refactoring pour cela. C'est dommage. En suivant ce modèle «chaque classe obtient une interface», non seulement cela produit du désordre, mais il manque complètement le point.
Une interface ne doit pas être considérée comme une simple reformulation formelle de tout ce que la classe peut faire. Une interface doit être considérée comme un contrat imposé par le code client utilisateur détaillant ses besoins.
Je n'ai aucun problème à écrire une interface qui n'a actuellement qu'une seule classe l'implémentant. En fait, je m'en fiche si aucune classe ne l'implémente encore. Parce que je pense à ce dont mon code a besoin. L'interface exprime ce que demande le code utilisateur. Tout ce qui arrivera plus tard peut faire ce qu'il veut tant qu'il répond à ces attentes.
Maintenant, je ne fais pas cela chaque fois qu'un objet en utilise un autre. Je le fais lors du franchissement d'une frontière. Je le fais quand je ne veux pas qu'un objet sache exactement à quel autre objet il parle. C'est la seule façon dont le polymorphisme fonctionnera. Je le fais quand je m'attends à ce que l'objet dont parle mon code client soit susceptible de changer. Je ne fais certainement pas cela lorsque j'utilise la classe String. La classe String est agréable et stable et je ne ressens pas le besoin de me prémunir contre tout changement sur moi.
Lorsque vous décidez d'interagir directement avec une implémentation concrète plutôt que par le biais d'une abstraction, vous prédisez que l'implémentation est suffisamment stable pour ne pas changer.
Ce droit là est la façon dont je tempère le principe d'inversion de dépendance . Vous ne devriez pas appliquer aveuglément et fanatiquement ceci à tout. Lorsque vous ajoutez une abstraction, vous dites vraiment que vous ne faites pas confiance au choix d'implémenter la classe pour être stable pendant la durée de vie du projet.
Tout cela suppose que vous essayez de suivre le principe ouvert et fermé . Ce principe n'est important que lorsque les coûts associés à la modification directe du code établi sont importants. L'une des principales raisons pour lesquelles les gens ne sont pas d'accord sur l'importance du découplage des objets est que tout le monde ne subit pas les mêmes coûts lors des changements directs. Si retester, recompiler et redistribuer l'intégralité de votre base de code est trivial pour vous, résoudre un besoin de changement avec une modification directe est probablement une simplification très intéressante de ce problème.
Il n'y a tout simplement pas de réponse cérébrale à cette question. Une interface ou une classe abstraite n'est pas quelque chose que vous devez ajouter à chaque classe et vous ne pouvez pas simplement compter le nombre de classes d'implémentation et décider qu'elle n'est pas nécessaire. Il s'agit de gérer le changement. Ce qui signifie que vous anticipez l'avenir. Ne soyez pas surpris si vous vous trompez. Restez simple comme vous pouvez sans vous reculer dans un coin.
Alors s'il vous plaît, n'écrivez pas d'abstractions juste pour nous aider à lire le code. Nous avons des outils pour ça. Utilisez des abstractions pour découpler ce qui doit être découplé.