Innanzitutto, capisci che una classe puramente astratta è in realtà solo un'interfaccia che non può fare l'ereditarietà multipla.
Scrivere classe, estrarre l'interfaccia, è un'attività cerebrale. Tanto che abbiamo un refactoring per questo. È un peccato. Seguendo questo schema "ogni classe ottiene un'interfaccia" non solo produce disordine ma manca completamente il punto.
Un'interfaccia non dovrebbe essere considerata semplicemente una riaffermazione formale di qualunque cosa la classe possa fare. Un'interfaccia dovrebbe essere pensata come un contratto imposto dall'uso del codice client che specifica i dettagli di cui ha bisogno.
Non ho alcun problema a scrivere un'interfaccia che attualmente ha solo una classe che la implementa. In realtà non mi interessa se nessuna classe lo implementa ancora. Perché sto pensando a cosa mi serve per usare il codice. L'interfaccia esprime ciò che richiede l'utilizzo del codice. Qualunque cosa succeda dopo, può fare ciò che gli piace purché soddisfi queste aspettative.
Ora non lo faccio ogni volta che un oggetto ne utilizza un altro. Lo faccio attraversando un confine. Lo faccio quando non voglio che un oggetto sappia esattamente con quale altro oggetto sta parlando. Qual è l'unico modo in cui il polimorfismo funzionerà. Lo faccio quando mi aspetto che l'oggetto che il mio codice client sta parlando possa cambiare. Certamente non lo faccio quando quello che sto usando è la classe String. La classe String è bella e stabile e non ho bisogno di guardarmi dal cambiarmi.
Quando decidi di interagire direttamente con un'implementazione concreta piuttosto che attraverso un'astrazione, prevedi che l'implementazione è abbastanza stabile da fidarti di non cambiare.
Proprio così c'è il modo in cui tempero il principio di inversione di dipendenza . Non dovresti applicare ciecamente fanaticamente questo a tutto. Quando aggiungi un'astrazione, stai davvero dicendo che non ti fidi della scelta della classe di implementazione per essere stabile durante la vita del progetto.
Tutto questo presuppone che tu stia cercando di seguire il principio aperto chiuso . Questo principio è importante solo quando i costi associati alla modifica diretta del codice stabilito sono significativi. Uno dei motivi principali per cui le persone non sono d'accordo su quanto sia importante il disaccoppiamento degli oggetti perché non tutti sostengono gli stessi costi quando apportano modifiche dirette. Se ripetere il test, ricompilare e ridistribuire l'intera base di codice è banale per te, risolvere una necessità di modifica con la modifica diretta è probabilmente una semplificazione molto interessante di questo problema.
Semplicemente non c'è una risposta cerebrale a questa domanda. Un'interfaccia o una classe astratta non è qualcosa che dovresti aggiungere ad ogni classe e non puoi semplicemente contare il numero di classi di implementazione e decidere che non è necessario. Si tratta di affrontare il cambiamento. Ciò significa che stai anticipando il futuro. Non essere sorpreso se sbagli. Mantienilo semplice come puoi senza tirarti indietro in un angolo.
Quindi, per favore, non scrivere astrazioni solo per aiutarci a leggere il codice. Abbiamo strumenti per questo. Usa le astrazioni per disaccoppiare ciò che deve essere disaccoppiato.