Als C ++ - Entwickler bin ich an C ++ - Headerdateien gewöhnt und finde es vorteilhaft, eine Art erzwungene "Dokumentation" im Code zu haben. Normalerweise habe ich eine schlechte Zeit, wenn ich etwas C # -Code lesen muss: Ich habe keine solche mentale Karte der Klasse, mit der ich arbeite.
Nehmen wir an, dass ich als Softwareentwickler das Framework eines Programms entwerfe. Wäre es zu verrückt, jede Klasse als abstrakte, nicht implementierte Klasse zu definieren, ähnlich wie wir es mit C ++ - Headern machen würden, und es den Entwicklern überlassen, sie zu implementieren?
Ich vermute, dass es einige Gründe gibt, warum dies für jemanden eine schreckliche Lösung sein könnte, aber ich bin mir nicht sicher, warum. Was müsste man für eine solche Lösung beachten?
quelle
Antworten:
Der Grund, warum dies in C ++ geschah, bestand darin, Compiler schneller und einfacher zu implementieren. Dies war kein Entwurf, um die Programmierung zu vereinfachen.
Der Zweck der Header-Dateien bestand darin, es dem Compiler zu ermöglichen, einen superschnellen ersten Durchlauf durchzuführen, um alle erwarteten Funktionsnamen zu kennen und ihnen Speicherorte zuzuweisen, damit sie beim Aufruf in cp-Dateien referenziert werden können, selbst wenn die Klasse, die sie definiert, diese hat wurde noch nicht analysiert.
Der Versuch, eine Folge alter Hardwareeinschränkungen in einer modernen Entwicklungsumgebung zu replizieren, wird nicht empfohlen!
Das Definieren einer Schnittstelle oder einer abstrakten Klasse für jede Klasse senkt Ihre Produktivität. Was hättest du sonst mit dieser Zeit anfangen können ? Auch andere Entwickler werden dieser Konvention nicht folgen.
Tatsächlich können andere Entwickler Ihre abstrakten Klassen löschen. Wenn ich in Code eine Schnittstelle finde, die diesen beiden Kriterien entspricht, lösche ich sie und überarbeite sie aus dem Code heraus:
1. Does not conform to the interface segregation principle
2. Only has one class that inherits from it
Die andere Sache ist, dass Visual Studio Tools enthält, die das tun, was Sie automatisch erreichen möchten:
Class View
Object Browser
Solution Explorer
können Sie auf Dreiecke klicken, um Klassen zu belegen und deren Funktionen, Parameter und Rückgabetypen anzuzeigen.Probieren Sie eines der oben genannten Verfahren aus, bevor Sie sich mit der Replikation von C ++ - Headerdateien in C # befassen.
Darüber hinaus gibt es technische Gründe, dies nicht zu tun. Dadurch wird Ihre endgültige Binärdatei größer, als es sein muss. Ich werde diesen Kommentar von Mark Benningfield wiederholen:
Auch, wie von Robert Harvey erwähnt, wäre das technisch nächstliegende Äquivalent eines Headers in C # eine Schnittstelle, keine abstrakte Klasse.
quelle
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.
quelle
Ja, das wäre schrecklich, weil (1) es unnötigen Code einführt. (2) es den Leser verwirren wird.
Wenn Sie in C # programmieren möchten, sollten Sie sich nur daran gewöhnen, C # zu lesen. Code, der von anderen Leuten geschrieben wurde, folgt sowieso nicht diesem Muster.
quelle
Unit-Tests
Ich empfehle dringend, Unit-Tests zu schreiben, anstatt den Code mit Interfaces oder abstrakten Klassen zu überfrachten (außer wenn dies aus anderen Gründen gerechtfertigt ist).
Ein gut geschriebener Komponententest beschreibt nicht nur die Schnittstelle Ihrer Klasse (wie es eine Header-Datei, eine abstrakte Klasse oder eine Schnittstelle tun würde), sondern beschreibt auch beispielhaft die gewünschte Funktionalität.
Beispiel: Wo Sie möglicherweise eine Header-Datei myclass.h geschrieben haben:
Schreiben Sie stattdessen in c # einen Test wie diesen:
Dieser sehr einfache Test vermittelt die gleichen Informationen wie die Header-Datei. (Wir sollten eine Klasse namens "MyClass" mit einer parameterlosen Funktion "Foo" haben.) Während eine Header-Datei kompakter ist, enthält der Test weitaus mehr Informationen.
Eine Einschränkung: Solch ein Prozess, bei dem ein leitender Softwareentwickler andere Entwickler mit (fehlgeschlagenen) Tests beauftragen muss, Konflikte mit Methoden wie TDD gewaltsam zu lösen, wäre in Ihrem Fall jedoch eine enorme Verbesserung.
quelle