Primero, comprenda que una clase puramente abstracta es realmente solo una interfaz que no puede hacer herencia múltiple.
Escribir clase, extraer interfaz, es una actividad con muerte cerebral. Tanto es así que tenemos una refactorización para ello. Lo cual es una pena. Seguir este patrón de "cada clase tiene una interfaz" no solo produce desorden, sino que pierde el punto por completo.
Una interfaz no debe considerarse simplemente como una reformulación formal de lo que la clase pueda hacer. Una interfaz debe considerarse como un contrato impuesto por el uso del código del cliente que detalla sus necesidades.
No tengo ningún problema para escribir una interfaz que actualmente solo tiene una clase que la implementa. De hecho, no me importa si ninguna clase lo implementa todavía. Porque estoy pensando en lo que necesita mi código de uso. La interfaz expresa lo que exige el código de uso. Lo que venga después puede hacer lo que quiera siempre y cuando satisfaga estas expectativas.
Ahora no hago esto cada vez que un objeto usa otro. Hago esto cuando cruzo un límite. Lo hago cuando no quiero que un objeto sepa exactamente con qué otro objeto está hablando. Cuál es la única forma en que funcionará el polimorfismo. Lo hago cuando espero que el objeto del código de mi cliente pueda cambiar. Ciertamente no hago esto cuando lo que estoy usando es la clase String. La clase String es agradable y estable y no siento la necesidad de evitar que me cambie.
Cuando decide interactuar directamente con una implementación concreta en lugar de hacerlo a través de una abstracción, predice que la implementación es lo suficientemente estable como para confiar en no cambiar.
Ahí está la forma en que atenúo el Principio de Inversión de Dependencia . No deberías aplicar esto ciegamente a todo fanáticamente. Cuando agrega una abstracción, realmente está diciendo que no confía en que la elección de implementar la clase sea estable durante la vida del proyecto.
Todo esto supone que está tratando de seguir el Principio Abierto Cerrado . Este principio es importante solo cuando los costos asociados con la realización de cambios directos al código establecido son significativos. Una de las principales razones por las que las personas no están de acuerdo sobre la importancia de los objetos de desacoplamiento es porque no todos experimentan los mismos costos al realizar cambios directos. Si volver a probar, volver a compilar y redistribuir su base de código completa es trivial para usted, entonces resolver una necesidad de cambio con modificación directa es probablemente una simplificación muy atractiva de este problema.
Simplemente no hay una respuesta cerebral a esta pregunta. Una interfaz o clase abstracta no es algo que debe agregar a cada clase y no puede simplemente contar el número de clases de implementación y decidir que no es necesario. Se trata de lidiar con el cambio. Lo que significa que estás anticipando el futuro. No te sorprendas si te equivocas. Hazlo lo más simple posible sin retroceder en una esquina.
Entonces, por favor, no escriba abstracciones solo para ayudarnos a leer el código. Tenemos herramientas para eso. Use abstracciones para desacoplar lo que necesita desacoplamiento.