Ich vermute, ich habe hier einen Schülerfehler gemacht und suche nach einer Klärung. Viele der Klassen in meiner Lösung (C #) - ich wage es, die Mehrheit zu sagen - haben am Ende eine entsprechende Schnittstelle für geschrieben. ZB eine "ICalculator" -Schnittstelle und eine "Calculator" -Klasse, die sie implementiert, obwohl ich diesen Rechner wahrscheinlich nie durch eine andere Implementierung ersetzen werde. Außerdem befinden sich die meisten dieser Klassen im selben Projekt wie ihre Abhängigkeiten - sie müssen es wirklich nur sein internal
, sind aber letztendlich public
ein Nebeneffekt bei der Implementierung ihrer jeweiligen Schnittstellen.
Ich denke, diese Praxis, Schnittstellen für alles zu schaffen, ist auf ein paar Lügen zurückzuführen:
1) Ich dachte ursprünglich, dass eine Schnittstelle notwendig ist, um Unit-Test-Mocks zu erstellen (ich verwende Moq), aber seitdem habe ich herausgefunden, dass eine Klasse verspottet werden kann, wenn ihre Mitglieder sind virtual
, und dass sie einen parameterlosen Konstruktor hat (korrigieren Sie mich, wenn Ich liege falsch).
2) Ich dachte ursprünglich, dass eine Schnittstelle notwendig ist, um eine Klasse beim IoC-Framework (Castle Windsor) zu registrieren, z
Container.Register(Component.For<ICalculator>().ImplementedBy<Calculator>()...
in der Tat konnte ich nur den konkreten Typ gegen sich selbst registrieren:
Container.Register(Component.For<Calculator>().ImplementedBy<Calculator>()...
3) Die Verwendung von Schnittstellen, z. B. Konstruktorparametern für die Abhängigkeitsinjektion, führt zu einer "losen Kopplung".
Also bin ich verrückt nach Schnittstellen geworden ?! Mir sind die Szenarien bekannt, in denen Sie "normalerweise" eine Schnittstelle verwenden würden, z. B. um eine öffentliche API verfügbar zu machen, oder für Dinge wie "steckbare" Funktionalität. Meine Lösung verfügt über eine kleine Anzahl von Klassen, die für solche Anwendungsfälle geeignet sind. Ich frage mich jedoch, ob alle anderen Schnittstellen unnötig sind und entfernt werden sollten. Verstoße ich in Bezug auf Punkt 3) nicht gegen "lose Kopplung", wenn ich dies tun würde?
Bearbeiten : - Ich habe gerade ein Spiel mit Moq, und es scheint, dass Methoden öffentlich und virtuell sein und einen öffentlichen parameterlosen Konstruktor haben müssen, um sie verspotten zu können. Es sieht also so aus, als ob ich keine internen Klassen haben kann?
quelle
Antworten:
Wenn Sie eine Schnittstelle mit nur einem Implementierer erstellen, schreiben Sie die Dinge im Allgemeinen nur zweimal und verschwenden Ihre Zeit. Schnittstellen alleine bieten keine lose Kopplung, wenn sie eng mit einer Implementierung verbunden sind. Wenn Sie diese Dinge in Komponententests verspotten möchten, ist dies in der Regel ein gutes Zeichen dafür, dass Sie in der Realität zwangsläufig mehr als einen Implementierer benötigen Code.
Ich würde es für einen Codegeruch halten, wenn fast alle Klassen Schnittstellen haben. Das heißt, sie arbeiten fast alle auf die eine oder andere Weise miteinander. Ich würde vermuten, dass die Klassen zu viel tun (da es keine Hilfsklassen gibt) oder Sie zu viel abstrahiert haben (oh, ich möchte eine Provider-Schnittstelle für die Schwerkraft, da sich das ändern könnte!).
quelle
1) Auch wenn konkrete Klassen verspottbar sind, müssen Sie dennoch Member erstellen
virtual
und einen parameterlosen Konstruktor bereitstellen, der möglicherweise auffällig und unerwünscht ist. Sie (oder neue Teammitglieder) werden baldvirtual
in jeder neuen Klasse systematisch Konstruktoren und parameterlose Konstruktoren hinzufügen , ohne sich Gedanken zu machen, nur weil "so funktioniert".Eine Schnittstelle ist IMO eine viel bessere Idee, wenn Sie eine Abhängigkeit verspotten müssen, da Sie die Implementierung so lange verschieben können, bis Sie sie wirklich benötigen, und einen schönen, klaren Vertrag über die Abhängigkeit abschließen können.
3) Wie ist das eine Lüge?
Ich glaube, Schnittstellen eignen sich hervorragend zum Definieren von Messaging-Protokollen zwischen zusammenarbeitenden Objekten. Es kann Fälle geben, in denen ihre Notwendigkeit in Frage gestellt wird, wie z. B. bei Domänenentitäten . Überall dort, wo Sie einen stabilen Partner haben (dh eine injizierte Abhängigkeit im Gegensatz zu einer transienten Referenz), mit der Sie kommunizieren müssen, sollten Sie im Allgemeinen die Verwendung einer Schnittstelle in Betracht ziehen.
quelle
Die Idee von IoC ist es, die Betontypen austauschbar zu machen. Selbst wenn Sie (im Moment) nur einen Rechner haben, der ICalculator implementiert, kann eine zweite Implementierung nur von der Schnittstelle und nicht von den Implementierungsdetails von Calculator abhängen.
Daher ist die erste Version Ihrer IoC-Container-Registrierung die richtige und die, die Sie in Zukunft verwenden sollten.
Wenn Sie Ihre Klassen öffentlich oder intern machen, ist das nicht wirklich verwandt, und das Gleiche gilt für das Moqing.
quelle
Ich würde mir wirklich mehr Sorgen darüber machen, dass Sie anscheinend das Bedürfnis verspüren, nahezu jeden Typ in Ihrem gesamten Projekt zu "verspotten".
Test-Doubles sind vor allem nützlich, um Ihren Code von der Außenwelt (Drittanbieter-Bibliotheken, Festplatten-E / A, Netzwerk-E / A usw.) oder von wirklich teuren Berechnungen zu isolieren. Wenn Sie mit Ihrem Isolations-Framework zu liberal sind (brauchen Sie es sogar WIRKLICH? Ist Ihr Projekt so komplex?), Kann dies leicht zu Tests führen, die an die Implementierung gekoppelt sind, und Ihr Design wird starrer. Wenn Sie eine Reihe von Tests schreiben, die überprüfen, ob eine Klasse die Methoden X und Y in dieser Reihenfolge aufgerufen und dann die Parameter a, b und c an die Methode Z übergeben hat, erhalten Sie dann WIRKLICH einen Wert? Manchmal erhalten Sie solche Tests, wenn Sie die Protokollierung oder Interaktion mit Bibliotheken von Drittanbietern testen. Dies sollte jedoch die Ausnahme und nicht die Regel sein.
Sobald Ihnen Ihr Isolations-Framework sagt, dass Sie etwas veröffentlichen müssen und dass Sie immer parameterlose Konstruktoren haben müssen, ist es an der Zeit, dem Teufel aus dem Weg zu gehen, da Ihr Framework Sie an diesem Punkt zwingt, schlechten (schlechteren) Code zu schreiben.
Ich spreche genauer über Schnittstellen und finde, dass sie in einigen Schlüsselfällen nützlich sind:
Wenn Sie Methodenaufrufe an verschiedene Typen senden müssen, z. B. wenn Sie mehrere Implementierungen haben (dies liegt auf der Hand, da die Definition einer Schnittstelle in den meisten Sprachen nur eine Sammlung virtueller Methoden ist).
Wenn Sie in der Lage sein müssen, den Rest Ihres Codes von einer Klasse zu isolieren. Dies ist die übliche Methode, um das Dateisystem, den Netzwerkzugriff, die Datenbank usw. von der Kernlogik Ihrer Anwendung zu trennen.
Wenn Sie die Steuerung umkehren müssen, um Anwendungsgrenzen zu schützen. Dies ist eine der leistungsstärksten Möglichkeiten zum Verwalten von Abhängigkeiten. Wir alle wissen, dass High-Level-Module und Low-Level-Module nichts voneinander wissen sollten, da sie sich aus unterschiedlichen Gründen ändern und Sie KEINE Abhängigkeiten von HttpContext in Ihrem Domain-Modell oder einem PDF-Rendering-Framework in Ihrer Matrix-Multiplikationsbibliothek haben möchten . Das Implementieren von Schnittstellen durch Grenzflächenklassen (auch wenn sie niemals ersetzt werden) erleichtert die Kopplung zwischen Ebenen und verringert das Risiko, dass Abhängigkeiten von Typen, die zu viele andere Typen kennen, durch die Ebenen sickern, drastisch.
quelle
Ich neige zu der Idee, dass, wenn eine Logik privat ist, die Implementierung dieser Logik für niemanden von Belang sein und als solche nicht getestet werden sollte. Wenn eine Logik so komplex ist, dass Sie sie testen möchten, hat diese Logik wahrscheinlich eine bestimmte Rolle und sollte daher öffentlich sein. Wenn ich z. B. Code mit einer privaten Hilfsmethode extrahiere, finde ich, dass dies normalerweise genauso gut in einer separaten öffentlichen Klasse (einem Formatierer, einem Parser, einem Mapper usw.) möglich ist.
Was die Schnittstellen angeht, lasse ich mich von der Notwendigkeit ablenken oder verspotten zu müssen. Sachen wie Repos möchte ich normalerweise stumm schalten oder verspotten können. Ein Mapper in einem Integrationstest (meistens) nicht so sehr.
quelle