Verstehen Sie zunächst, dass eine rein abstrakte Klasse eigentlich nur eine Schnittstelle ist, die keine Mehrfachvererbung durchführen kann.
Schreibe Klasse, extrahiere Schnittstelle, ist eine gehirntote Aktivität. So sehr, dass wir ein Refactoring dafür haben. Welches ist schade. Diesem Muster "Jede Klasse bekommt eine Schnittstelle" zu folgen, erzeugt nicht nur Unordnung, sondern verfehlt den Punkt vollständig.
Eine Schnittstelle sollte nicht einfach als formale Neuformulierung dessen betrachtet werden, was die Klasse tun kann. Eine Schnittstelle sollte als Vertrag betrachtet werden, der durch die Verwendung von Client-Code auferlegt wird, in dem die Anforderungen aufgeführt sind.
Ich habe überhaupt keine Probleme damit, ein Interface zu schreiben, das derzeit nur von einer Klasse implementiert wird. Es ist mir eigentlich egal, ob es noch keine Klasse gibt, die das umsetzt. Weil ich darüber nachdenke, was mein Code braucht. Die Schnittstelle drückt aus, was der Verwendungscode erfordert. Was später kommt, kann machen, was es will, solange es diese Erwartungen erfüllt.
Jetzt mache ich das nicht jedes Mal, wenn ein Objekt ein anderes verwendet. Ich mache das, wenn ich eine Grenze überschreite. Ich mache es, wenn ein Objekt nicht genau wissen soll, mit welchem anderen Objekt es spricht. Nur so kann Polymorphismus funktionieren. Ich mache es, wenn ich erwarte, dass sich das Objekt, von dem mein Client-Code spricht, wahrscheinlich ändert. Ich mache das auf keinen Fall, wenn ich die String-Klasse verwende. Die String-Klasse ist nett und stabil und ich habe nicht das Bedürfnis, mich davor zu hüten, dass sie sich auf mich auswirkt.
Wenn Sie sich entscheiden, direkt mit einer konkreten Implementierung zu interagieren, und nicht über eine Abstraktion, gehen Sie davon aus, dass die Implementierung stabil genug ist, um darauf zu vertrauen, dass sie sich nicht ändert.
Genau so tempere ich das Prinzip der Abhängigkeitsinversion . Sie sollten dies nicht blind fanatisch auf alles anwenden. Wenn Sie eine Abstraktion hinzufügen, sagen Sie wirklich, dass Sie nicht darauf vertrauen, dass die implementierende Klasse über die gesamte Laufzeit des Projekts stabil bleibt.
Dies alles setzt voraus, dass Sie versuchen, dem Open Closed-Prinzip zu folgen . Dieses Prinzip ist nur dann wichtig, wenn die Kosten für direkte Änderungen am etablierten Code erheblich sind. Einer der Hauptgründe, warum sich die Leute nicht darüber einig sind, wie wichtig das Entkoppeln von Objekten ist, ist, dass nicht jeder bei direkten Änderungen die gleichen Kosten verursacht. Wenn das erneute Testen, Neukompilieren und Verteilen Ihrer gesamten Codebasis für Sie trivial ist, ist das Lösen eines Änderungsbedarfs durch direkte Änderung wahrscheinlich eine sehr attraktive Vereinfachung dieses Problems.
Es gibt einfach keine gehirntote Antwort auf diese Frage. Eine Schnittstelle oder eine abstrakte Klasse sollten Sie nicht jeder Klasse hinzufügen, und Sie können nicht einfach die Anzahl der implementierenden Klassen zählen und entscheiden, dass sie nicht benötigt werden. Es geht um den Umgang mit Veränderung. Was bedeutet, dass Sie die Zukunft vorwegnehmen. Sei nicht überrascht, wenn du etwas falsch machst. Halte es so einfach wie möglich, ohne dich in eine Ecke zurückzulehnen.
Schreiben Sie also bitte keine Abstraktionen, um uns beim Lesen des Codes zu helfen. Wir haben Werkzeuge dafür. Verwenden Sie Abstraktionen, um zu entkoppeln, was entkoppelt werden muss.