Warum sind nicht alle Methoden virtuell oder warum hat nicht jede Klasse mindestens eine Schnittstelle?

8

Dies ist eine eher philosophische Frage, die sich mit der .NET-Plattform befasst, aber möglicherweise ist sie auch für andere Sprachen nützlich. Ich mache viele Unit-Tests und vor allem, wenn ich Komponenten von Drittanbietern verwende, mit denen ich oft zu kämpfen habe. In .NET besteht ein enormer Anspruch auf Komponentendesign (er), zu entscheiden, welche Methode virtuell sein soll oder nicht. Auf der einen Seite ist es die Verwendung von Komponenten (was sinnvoll ist, um virtuell zu sein), auf der anderen Seite die Verspottbarkeit von Komponenten . Sie können Shims verwenden , um Komponenten von Drittanbietern zu verspotten. Dies führt jedoch häufig zu schlechtem Design und Komplexität. Wie ich mich erinnern kann, ging es bei der Diskussion darüber, alle Methoden virtuell zu machen (JAVA hat es von Anfang an) in .NET um Leistung. Aber es ist immer noch das Problem? Warum sind nicht alle Methoden in .NET virtuell oder warum hat nicht jede Klasse mindestens eine Schnittstelle?

Anton Kalcik
quelle
1
Ah ja, denn nach ein paar Minuten hat mich jemand (mein erstes Mal) herabgestimmt. Ich würde gerne wissen warum.
Anton Kalcik
Ich vermute, die Person, die dafür gestimmt hat, diese Frage als "zu weit gefasst" zu schließen, hat Sie ebenfalls abgelehnt.
David Arno
1
"Programmers Stack Exchange ist eine Frage- und Antwortseite für professionelle Programmierer, die sich für konzeptionelle Fragen zur Softwareentwicklung interessieren." Habe ich etwas falsch verstanden?
Anton Kalcik
5
Ich stimme eher zu, dass diese Frage zu weit gefasst ist. "Warum hat nicht jede Klasse eine virtuelle?" Warum sollte es? Sie erwähnen das Testen. Vielleicht könnten Sie die Frage verbessern, indem Sie sie in "Warum hat diese Klasse (die nervigste .net-Klasse) diese Methoden nicht als virtuell markiert" ändern? Sie können jedoch herausfinden, dass die Antwort "Oh ja. Sie haben die Explosion von TDD beim Entwerfen von .NET 1.0 nicht wirklich erwartet"
Perfektionist
4
Wenn Sie es breit machen möchten, weil Sie nur an Meinungen interessiert sind, dann ist es aus zwei Gründen nicht zum Thema : zu breit und meinungsbasiert .
Jörg W Mittag

Antworten:

7

Wie Anders sagt , geht es zum Teil um Leistung und zum Teil darum, schlecht durchdachte Entwürfe zu sperren, um den Umfang der Probleme zu verringern, die später von Menschen verursacht werden, die blind Dinge erben, die nicht für die Vererbung konzipiert wurden.

Die Leistung scheint offensichtlich zu sein - während die meisten Methoden auf moderner Hardware nicht bemerkt werden, kann ein virtueller Getter selbst bei moderner Hardware, die sich immer mehr auf leicht vorhersehbare Anweisungen in der CPU-Pipeline stützt, einen spürbaren Erfolg verursachen.

Der Grund, alles standardmäßig virtuell zu machen, scheint nur eines zu sein: Unit-Testing-Frameworks. Es scheint mir, dass dies ein schlechter Grund ist, wo wir den Code an das Framework anpassen, anstatt ihn richtig zu entwerfen.

Vielleicht liegt das Problem bei den hier verwendeten Frameworks. Sie sollten verbessert werden, um das Ersetzen von Funktionen zur Laufzeit durch injizierte Shims zu ermöglichen, anstatt Code zu erstellen, der dies tut (mit allen Hacks, um private Methoden zu umgehen, sowie nicht-virtuelle, nicht um Erwähnen Sie statische Funktionen und Funktionen von Drittanbietern.

Der Grund, warum Methoden standardmäßig nicht virtuell sind, ist wie Anders sagte, und jeder Wunsch, sie virtuell zu machen, um sie an ein Unit-Test-Tool anzupassen, stellt den Wagen vor das Pferd. Das richtige Design schlägt künstliche Einschränkungen.

Jetzt können Sie all dies abmildern, indem Sie Schnittstellen verwenden oder Ihre gesamte Codebasis überarbeiten, um Komponenten (oder Microservices) zu verwenden, die über die Nachrichtenübermittlung anstelle von direkt verkabelten Methodenaufrufen kommunizieren. Wenn Sie die Oberfläche einer Einheit zum Testen vergrößern, um eine Komponente zu sein, und diese Komponente vollständig in sich geschlossen ist, müssen Sie nicht wirklich damit schrauben, um sie zu testen.

gbjbaanb
quelle
Ich konnte nicht mehr zustimmen.
Robert Harvey
@gbjbaanb Ich habe den letzten Satz nicht verstanden. "Wenn Sie die Oberfläche einer Einheit zum Testen vergrößern, um eine Komponente zu sein, und diese Komponente vollständig in sich geschlossen ist, müssen Sie nicht wirklich damit schrauben, um sie zu testen." ""
Anton Kalcik
Ausgezeichnete Antwort (besser als meine, ich fühle mich :)
David Arno
1
@AntonKalcik Ich meine, wenn Sie eine Black Box haben, sollte sie keine Abhängigkeiten haben, die verspottet werden müssen. Das heißt, Sie testen es, indem Sie Eingaben auslösen und sehen, was herauskommt. Wenn Ihre Box klein ist und Sie sie so konzipiert haben, dass sie von anderen Diensten entkoppelt wird, kann dies leicht passieren. Das Problem ist, dass eine Klasse nicht sehr leicht von anderen Klassen getrennt werden kann und daher verspottet werden muss. Wenn Sie sich eine Einheit als Komponente (vielleicht eine DLL oder einen Microservice) und nicht als Klasse vorstellen, können Sie auf diese Weise testen.
Gbjbaanb
In Bezug auf die Leistung: Die CPU kann den Aufruf virtueller Methoden leicht vorhersagen (wenn es immer derselbe ist). Das große Problem ist, dass es für den JIT-Compiler viel schwieriger ist, virtuelle Methodenaufrufe zu integrieren, wodurch weitere Optimierungen verhindert werden.
Svick
6

Ihre Frage enthält zwei Elemente, daher werde ich versuchen, sie der Reihe nach zu beantworten:

  1. Warum sind .NET-Methoden nicht standardmäßig virtuell?

    Vererbung hat in der Praxis viele Probleme . Wenn es also als Teil eines Entwurfs verwendet werden soll, sollte es sorgfältig abgewogen werden. Indem Methoden standardmäßig nicht virtuell gemacht werden, wird der Entwickler theoretisch dazu ermutigt, beim Entwerfen einer Klasse über Vererbung nachzudenken. Ob das in der Praxis funktioniert, ist selbstverständlich.

  2. Warum implementieren nicht alle Klassen eine Schnittstelle?

    Schlechtes Design ist die einfache Antwort. Obwohl sie lange Zeit allgemein als bewährte Verfahren anerkannt wurden, entwerfen viele Leute ihre Systeme leider immer noch nicht mit Blick auf DI und TDD.

Die Kombination von Klassen, die nicht vollständig vererbbar sind und nicht leicht verspottet werden können, erzeugt einen "perfekten Sturm" von schwer zu testendem Code. Bis wir jedoch den Tag erreichen, an dem jeder DI und das Design-to-Interface-Prinzip verwendet, kann nicht viel getan werden, um die Schmerzen zu lindern.

David Arno
quelle
3
"Schlechtes Design ist die einfache Antwort" - ich stimme eher zu, aber es gibt Ausnahmen. Siehe Datenübertragungsobjekte oder Datensatzobjekte. Für einfache POCO / POJO / Sprache hier Objekte einfügen ist es nicht sinnvoll, ein interfacenur weil zu implementieren . Welchen Wert hat es, ein interfacefür das zu haben, was im Wesentlichen ein verherrlichtes Tupel ist? (Obwohl die Verwendung eines POJO / POCO selbst möglicherweise ein Konstruktionsfehler ist)
Dan Pantry,
2
@AntonKalcik, das C # -Team verfolgt einen sehr konservativen Ansatz in Bezug auf die Sprache, bei dem es darum geht, Schnittstellen obligatorisch zu machen. Ich vermute also, dass Sie nicht sehen werden, dass dies zu C # hinzugefügt wurde.
David Arno
3
@DanPantry, einfache Datenobjekte müssen jedoch selten verspottet werden, sodass normalerweise keine Schnittstellen erforderlich sind.
David Arno
2
In C♯ definieren Schnittstellen Objekte, Klassen definieren abstrakte Datentypen. (Es ist ein weit verbreitetes Missverständnis, dass Klassen das sind, was Sie in C♯ zum Schreiben von objektorientiertem Code verwenden. Im Gegenteil: OO-Code in C♯ kann niemals Klassen oder Strukturen als Typen verwenden, sondern nur Schnittstellen. Sobald Sie Klassen als Typen verwenden , du machst kein OO mehr.) Also würde ich es nicht als Designfehler bezeichnen, keine Schnittstellen zu verwenden. Es ist eine Designentscheidung: ADTs und Objekte haben zwei Stärken und Schwächen. Sie sollten auswählen, was in Ihrer speziellen Domain sinnvoller ist.
Jörg W Mittag
5
@ JörgWMittag: As soon as you use classes as types, you are no longer doing OO - Klassen sind Typen. Was?? Die normale Klassenvererbung ist OO, egal was Sie sagen. Sie können sich nicht mehr entscheiden, dass es nicht mehr OO ist, nur weil DI jetzt der letzte Schrei ist.
Robert Harvey