Um austauschbar und testbar zu sein, müssen Dienste mit Logik normalerweise eine Schnittstelle haben, z
public class FooService: IFooService
{ ... }
In Bezug auf das Design stimme ich dem zu, aber eines der Dinge, die mich bei diesem Ansatz stören, ist, dass Sie für einen Service zwei Dinge deklarieren müssen (die Klasse und die Schnittstelle) und in unserem Team normalerweise zwei Dateien (eine) für die Klasse und eine für die Schnittstelle). Ein weiteres Problem ist die Schwierigkeit bei der Navigation, da die Verwendung von "Gehe zu Definition" in IDE (VS2010) auf die Schnittstelle verweist (da sich andere Klassen auf die Schnittstelle beziehen), nicht auf die tatsächliche Klasse.
Ich dachte, dass das Schreiben von IFooService in die gleiche Datei wie FooService die oben genannte Verrücktheit reduzieren wird. Immerhin sind IFooService und FooService sehr verwandt. Ist das eine gute Übung? Gibt es einen guten Grund, warum sich IFooService in einer eigenen Datei befinden muss?
quelle
Antworten:
Es muss nicht in einer eigenen Datei sein, aber Ihr Team sollte sich für einen Standard entscheiden und sich daran halten.
Sie haben auch Recht, dass Sie mit "Gehe zur Definition" zur Benutzeroberfläche gelangen. Wenn Sie jedoch Resharper installiert haben, ist es nur ein Klick, um eine Liste der von dieser Benutzeroberfläche abgeleiteten Klassen / Benutzeroberflächen zu öffnen. Deshalb behalte ich die Schnittstelle in einer separaten Datei.
quelle
Ich denke, Sie sollten sie getrennt aufbewahren. Wie Sie sagten, besteht die Idee darin, testbar und austauschbar zu bleiben. Indem Sie die Schnittstelle in derselben Datei wie Ihre Implementierung ablegen, verknüpfen Sie die Schnittstelle mit der spezifischen Implementierung. Sollten Sie sich entscheiden, ein Scheinobjekt oder eine andere Implementierung zu erstellen, wird die Schnittstelle nicht logisch von FooService getrennt.
quelle
Laut SOLID sollte die Schnittstelle nicht nur in einer anderen Datei, sondern auch in einer anderen Assembly erstellt werden.
Warum? Denn jede Änderung an einer Quelldatei, die in eine Assembly kompiliert wird, erfordert eine Neukompilierung der Assembly, und jede Änderung an einer Assembly erfordert eine Neukompilierung einer abhängigen Assembly. Wenn Sie also basierend auf SOLID eine Implementierung A durch eine Implementierung B ersetzen möchten, während die Klasse C von der Schnittstelle abhängt, muss ich den Unterschied nicht kennen, und Sie müssen sicherstellen, dass die Assembly mit I erstellt wird Daran ändert sich nichts, wodurch die Verwendungen geschützt werden.
"Aber es ist nur eine Neukompilierung", höre ich Sie protestieren. Das mag sein, aber in Ihrer Smartphone-App, die die Datenbandbreite Ihrer Benutzer schont. Laden Sie eine Binärdatei herunter, die sich geändert hat, oder laden Sie diese Binärdatei und fünf andere herunter, deren Code davon abhängt? Nicht jedes Programm ist so geschrieben, dass es von Desktop-Computern in einem LAN verwendet werden kann. Sogar in diesem Fall, in dem Bandbreite und Speicher günstig sind, können kleinere Patch-Versionen von Nutzen sein, da sie über Active Directory oder ähnliche Domänenverwaltungsebenen problemlos in das gesamte LAN übertragen werden können. Ihre Benutzer werden nur einige Sekunden auf die Anwendung warten, wenn sie sich das nächste Mal anmelden, anstatt ein paar Minuten, damit das Ganze erneut installiert werden kann. Ganz zu schweigen davon, dass je weniger Assemblys beim Erstellen eines Projekts neu kompiliert werden müssen, desto schneller erfolgt die Erstellung.
Nun zum Haftungsausschluss: Dies ist nicht immer möglich oder machbar. Am einfachsten ist es, ein zentrales "Schnittstellen" -Projekt zu erstellen. Dies hat seine eigenen Nachteile; Code wird weniger wiederverwendbar, da das Schnittstellenprojekt UND das Implementierungsprojekt in anderen Apps referenziert werden müssen, wobei die Persistenzschicht oder andere Schlüsselkomponenten Ihrer App wiederverwendet werden. Sie können dieses Problem überwinden, indem Sie die Schnittstellen in enger gekoppelte Assemblys aufteilen. Dann haben Sie jedoch mehr Projekte in Ihrer App, was einen vollständigen Build sehr schmerzhaft macht. Der Schlüssel ist das Gleichgewicht und die Beibehaltung des locker gekoppelten Designs. Normalerweise können Sie Dateien nach Bedarf verschieben. Wenn Sie also feststellen, dass eine Klasse viele Änderungen benötigt, oder dass regelmäßig neue Implementierungen einer Schnittstelle erforderlich sind (möglicherweise zur Verbindung mit neu unterstützten Versionen anderer Software),
quelle
Warum ist es unangenehm, separate Dateien zu haben? Für mich ist es viel ordentlicher und sauberer. Es ist üblich, einen Unterordner mit dem Namen "Interfaces" zu erstellen und Ihre IFooServer.cs-Dateien dort abzulegen, wenn Sie weniger Dateien in Ihrem Projektmappen-Explorer anzeigen möchten.
Der Grund, warum die Schnittstelle in einer eigenen Datei definiert ist, ist derselbe, warum Klassen normalerweise in einer eigenen Datei definiert sind: Die Projektverwaltung ist einfacher, wenn Ihre logische Struktur und Dateistruktur identisch sind, sodass Sie immer wissen, welche Datei eine bestimmte Klasse definiert Dies kann Ihnen das Leben beim Debuggen erleichtern (Ausnahmestapel-Traces geben normalerweise Datei- und Zeilennummern an) oder beim Zusammenführen von Quellcode in einem Quellcodeverwaltungs-Repository.
quelle
Es ist oft eine gute Praxis, Ihre Codedateien nur eine einzelne Klasse oder eine einzelne Schnittstelle enthalten zu lassen. Diese Codierungspraktiken sind jedoch ein Mittel zum Zweck - um Ihren Code besser zu strukturieren und die Arbeit damit zu erleichtern. Wenn Sie und Ihr Team es einfacher finden, mit ihnen zu arbeiten, wenn die Klassen mit ihren Schnittstellen zusammengehalten werden, tun Sie dies auf jeden Fall.
Persönlich bevorzuge ich es, die Schnittstelle und die Klasse in derselben Datei zu haben, wenn es nur eine Klasse gibt, die die Schnittstelle implementiert, wie in Ihrem Fall.
In Bezug auf die Probleme, die Sie mit der Navigation haben, kann ich ReSharper wärmstens empfehlen . Es enthält einige sehr nützliche Verknüpfungen, mit denen Sie direkt zu der Methode springen können, die eine bestimmte Schnittstellenmethode implementiert.
quelle
In vielen Fällen möchten Sie, dass sich Ihre Schnittstelle nicht nur in einer separaten Datei für die Klasse befindet, sondern sogar in einer separaten Assembly insgesamt.
Zum Beispiel kann ein WCF - Service - Vertrag Schnittstelle werden geteilt durch Client und Service , wenn Sie die Kontrolle über beide Enden des Drahtes haben. Durch Verschieben der Schnittstelle in eine eigene Assembly werden weniger eigene Assembly-Abhängigkeiten erzeugt. Dies erleichtert dem Client den Verbrauch erheblich und lockert die Kopplung mit ihrer Implementierung.
quelle
Es ist selten, wenn überhaupt, sinnvoll, eine einzige Implementierung einer Schnittstelle zu haben 1 . Wenn Sie eine öffentliche Schnittstelle und eine öffentliche Klasse, die diese Schnittstelle implementiert, in dieselbe Datei einfügen, ist es gut möglich, dass Sie keine Schnittstelle benötigen.
Wenn die Klasse, die Sie gemeinsam mit der Schnittstelle suchen , abstrakt ist und Sie wissen, dass alle Implementierungen Ihrer Schnittstelle diese abstrakte Klasse erben sollten, ist es sinnvoll, die beiden in derselben Datei zu suchen. Sie sollten Ihre Entscheidung, eine Schnittstelle zu verwenden, dennoch hinterfragen: Fragen Sie sich, ob eine abstrakte Klasse für sich allein in Ordnung wäre, und lassen Sie die Schnittstelle fallen, wenn die Antwort positiv ist.
Im Allgemeinen sollten Sie sich jedoch an die Strategie "Eine öffentliche Klasse / Schnittstelle entspricht einer Datei" halten: Sie ist einfach zu befolgen und erleichtert die Navigation in Ihrem Quelltext.
1 Eine bemerkenswerte Ausnahme ist, wenn Sie zu Testzwecken eine Schnittstelle benötigen, da Ihre Wahl des Spott-Frameworks diese zusätzliche Anforderung an Ihren Code gestellt hat.
quelle
sealed
. Wenn Sie den Verdacht haben, dass Sie zu einem späteren Zeitpunkt eine zweite Unterklasse hinzufügen, fügen Sie sofort ein Interface hinzu. PS Ein anständiger Refactoring-Assistent kann Ihnen zu einem bescheidenen Preis für eine einzelne ReSharper-Lizenz gehören :)virtual
.Schnittstellen gehören zu ihren Kunden, nicht zu ihren Implementierungen, wie in Agile Principles, Practices und Patterns von Robert C. Martin definiert. Daher verstößt die Kombination von Schnittstelle und Implementierung an derselben Stelle gegen das Prinzip.
Der Client-Code hängt von der Schnittstelle ab. Dies ermöglicht das Kompilieren und Bereitstellen des Client-Codes und der Schnittstelle ohne Implementierung. Dann können Sie verschiedene Implementierungen haben, die wie Plugins für den Client-Code funktionieren .
UPDATE: Dies ist hier kein Agile-Only-Prinzip. Die vierköpfige Bande in Design Patterns aus dem Jahr 1994 spricht bereits von Kunden, die sich an Schnittstellen halten und an Schnittstellen programmieren. Ihre Ansicht ist ähnlich.
quelle
Ich persönlich mag das "Ich" vor einer Schnittstelle nicht. Als Benutzer eines solchen Typs möchte ich nicht sehen, ob dies eine Schnittstelle ist oder nicht. Der Typ ist das Interessante. In Bezug auf Abhängigkeiten ist Ihr FooService EINE mögliche Implementierung von IFooService. Die Schnittstelle sollte sich in der Nähe des Ortes befinden, an dem sie verwendet wird. Andererseits sollte die Implementierung an einem Ort sein, an dem sie leicht geändert werden kann, ohne den Client zu beeinträchtigen. Daher würde ich zwei Dateien bevorzugen - normalerweise.
quelle
new
. Andererseits kann ich ihn auch als Co- oder Contra-Variante verwenden. Und dasI
ist eine in .Net etablierte Namenskonvention, daher ist es ein guter Grund, sich daran zu halten, wenn auch nur aus Gründen der Konsistenz mit anderen Typen.I
in interface zu beginnen war nur die Einführung in meine Argumente zur Trennung in zwei Dateien. Der Code ist besser lesbar und macht die Inversion der Abhängigkeiten deutlicher.I
in Ihrer Umgebung normal ist: Verwenden Sie es! (Aber ich mag es nicht :))Die Kodierung von Schnittstellen kann schlecht sein, wird in der Tat als Anti-Pattern für bestimmte Kontexte behandelt und ist kein Gesetz. In der Regel ist ein gutes Beispiel für die Codierung in Anti-Pattern-Interfaces, wenn Sie ein Interface haben, das nur eine Implementierung in Ihrer Anwendung hat. Eine andere wäre, wenn die Schnittstellendatei so lästig wird, dass Sie ihre Deklaration in der Datei der implementierten Klasse verstecken müssen.
quelle
Indem Sie eine Klasse und eine Schnittstelle in dieselbe Datei einfügen, können Sie beide Funktionen ohne Einschränkungen verwenden. Eine Schnittstelle kann weiterhin auf die gleiche Weise für Mocks usw. verwendet werden, auch wenn sie sich in derselben Datei befindet wie eine Klasse, die sie implementiert. Die Frage könnte nur als organisatorische Bequemlichkeit aufgefasst werden. In diesem Fall würde ich sagen, tun Sie, was auch immer Ihnen das Leben erleichtert!
Natürlich könnte man argumentieren, dass die beiden in derselben Datei die Unachtsamkeit hervorrufen könnten, sie als programmgesteuerter zu betrachten, als sie es tatsächlich sind, was ein Risiko darstellt.
Wenn ich persönlich auf eine Codebasis stoßen würde, die eine Konvention über die andere verwendet, wäre ich in keiner Weise zu besorgt.
quelle