Muss ich eine Schnittstelle verwenden, wenn sie jemals nur von einer Klasse implementiert wird?

243

Ist das nicht der springende Punkt einer Schnittstelle, bei der mehrere Klassen eine Reihe von Regeln und Implementierungen einhalten?

Lamin Sanneh
quelle
16
Oder um es einfacher zu machen, unittest.
11
Die Implementierung von Interfaces durch mehrere Klassen und die Verwendung von Code in Abhängigkeit von den Interfaces ist für den Komponententest von wesentlicher Bedeutung. Wenn Sie Unit-Tests durchführen, wird diese Schnittstelle von einer anderen Klasse implementiert.
StuperUser
2
Reddit Diskussion zu dieser Frage.
Yannis
6
Öffentliche Felder und Methoden sind eine eigenständige "Schnittstelle". Wenn der Mangel an Polymorphismus absichtlich geplant ist, gibt es keinen Grund, eine Schnittstelle zu verwenden. Die Unit-Tests, die von anderen erwähnt wurden, sind eine geplante Verwendung von Polymorphismus.
mike30

Antworten:

205

Genau genommen, nein, tust du nicht, YAGNI gilt. Der Zeitaufwand für die Erstellung der Benutzeroberfläche ist jedoch minimal, insbesondere wenn Sie ein praktisches Tool zur Codegenerierung haben, das den größten Teil der Arbeit für Sie erledigt. Wenn Sie sich nicht sicher sind, ob Sie die Schnittstelle von benötigen oder nicht, ist es besser, die Definition einer Schnittstelle zu unterstützen.

Darüber hinaus bietet die Verwendung einer Schnittstelle auch für eine einzelne Klasse eine weitere Scheinimplementierung für Komponententests, die nicht in Betrieb ist. Die Antwort von Avner Shahar-Kashtan wird in diesem Punkt erweitert.

yannis
quelle
84
+1 Testen bedeutet, dass Sie sowieso fast immer zwei Implementierungen haben
jk.
24
@YannisRizos Stimme deinem letzten Punkt wegen Yagni nicht zu. Das Starten eines Interfaces aus öffentlichen Methoden einer Klasse heraus ist trivial, ebenso wie das Ersetzen von CFoo durch IFoo beim Konsumieren von Klassen. Es hat keinen Sinn, es vor der Notwendigkeit zu schreiben.
Dan Neely
5
Ich bin mir immer noch nicht sicher, ob ich deiner Argumentation folge. Da Tools zur Codegenerierung das Hinzufügen später sogar noch billiger machen, sehe ich noch weniger einen Grund, die Schnittstelle zu erstellen, bevor Sie eine ausdrückliche Notwendigkeit dafür haben.
Dan Neely
6
Ich denke, ein fehlendes Interface ist kein gutes Beispiel für YAGNI, sondern für ein "kaputtes Fenster" und eine fehlende Dokumentation. Die Benutzer der Klasse sind praktisch gezwungen, gegen die Implementierung zu codieren, anstatt die Abstraktion wie sie sollte.
Fabio Fracassi
10
Warum würden Sie jemals Ihre Codebasis mit bedeutungsloser Cruft verschmutzen, um ein Test-Framework zu erfüllen? Ich meine im Ernst, ich bin nur ein autodidaktischer JavaScript-Client-Typ, der versucht, WTF zu beheben. Das stimmt nicht mit den OOD-Implementierungen von C # und Java-Entwicklern. Schlagen sie Ihnen die IDE aus den Händen und lassen Sie sie nicht zurück, bis Sie gelernt haben, sauberen, lesbaren Code zu schreiben, wenn sie Sie dabei erwischen, so etwas im College zu tun? Das ist einfach obszön.
Erik Reppen
144

Ich würde antworten, ob Sie eine Schnittstelle benötigen oder nicht, hängt nicht davon ab, wie viele Klassen sie implementieren. Schnittstellen sind ein Tool zum Definieren von Verträgen zwischen mehreren Subsystemen Ihrer Anwendung. Entscheidend ist also, wie Ihre Anwendung in Subsysteme unterteilt ist. Es sollte Schnittstellen als Front-End für gekapselte Subsysteme geben, unabhängig davon, wie viele Klassen sie implementieren.

Hier ist eine sehr nützliche Faustregel:

  • Wenn Sie dafür sorgen, dass sich die Klasse Foodirekt auf die Klasse bezieht BarImpl, verpflichten Sie sich nachdrücklich, sich Foojedes Mal zu ändern, wenn Sie sich ändern BarImpl. Sie behandeln sie im Grunde als eine Codeeinheit, die auf zwei Klassen aufgeteilt ist.
  • Wenn Sie Foosich auf die Benutzeroberfläche beziehen, Barverpflichten Sie sich stattdessen, Änderungen beim Ändern zu vermeiden .FooBarImpl

Wenn Sie Schnittstellen an den Schlüsselpunkten Ihrer Anwendung definieren, überlegen Sie genau, welche Methoden diese unterstützen sollen und welche nicht, und kommentieren Sie die Schnittstellen klar, um zu beschreiben, wie sich eine Implementierung verhalten soll (und wie nicht). Ihre Anwendung wird viel einfacher zu verstehen, weil diese kommentiert Schnittstellen eine Art bieten wird Spezifikation der anwendungs eine Beschreibung, wie es beabsichtigt zu verhalten. Dies macht es viel einfacher, den Code zu lesen (anstatt zu fragen, was zum Teufel dieser Code tun soll, können Sie fragen, wie dieser Code das tut, was er tun soll).

Darüber hinaus (oder gerade deswegen) fördern Schnittstellen die separate Kompilierung. Da die Kompilierung von Schnittstellen trivial ist und weniger Abhängigkeiten aufweist als deren Implementierungen, bedeutet dies, dass Sie normalerweise eine Neukompilierung durchführen können, ohne dass eine Neukompilierung erforderlich ist, wenn Sie eine Klasse Foozur Verwendung einer Schnittstelle schreiben . In großen Anwendungen kann dies viel Zeit sparen.BarBarImplFoo

Sacundim
quelle
14
Ich würde dies viel mehr als einmal befürworten, wenn ich könnte. IMO die beste Antwort auf diese Frage.
Fabio Fracassi
4
Wenn die Klasse nur eine Rolle ausführt (dh nur eine Schnittstelle dafür definiert haben würde), warum dann nicht über öffentliche / private Methoden?
Matthew Finlay
4
1. Code-Organisation; Die Schnittstelle in einer eigenen Datei zu haben, die nur Signaturen und Dokumentationskommentare enthält, hilft dabei, den Code sauber zu halten. 2. Frameworks, die Sie dazu zwingen, Methoden bereitzustellen, die Sie lieber privat halten möchten (z. B. Container, die Abhängigkeiten durch öffentliche Setter einschleusen). 3. Separate Zusammenstellung, wie bereits erwähnt; wenn Klasse Fooauf der Schnittstelle hängt Bardann BarImplkann modifiziert werden , ohne neu kompilieren zu müssen Foo. 4. Feinere Zugriffskontrolle als bei öffentlichen / privaten Angeboten (die gleiche Klasse über verschiedene Schnittstellen zwei Clients zur Verfügung stellen).
Sacundim
3
Und letztes: 5. Clients sollten sich (im Idealfall) nicht darum kümmern, wie viele Klassen mein Modul hat oder wie viele oder sogar in der Lage sind, dies zu sagen. Alles, was sie sehen sollten, sind eine Reihe von Typen und einige Fabriken oder Fassaden, um den Ball ins Rollen zu bringen. Das heißt, ich sehe oft einen Wert darin, selbst zu kapseln, aus welchen Klassen Ihre Bibliothek besteht.
Sacundim
4
@sacundim If you make class Foo refer directly to class BarImpl, you're strongly committing yourself to change Foo every time you change BarImplWelche Änderungen können beim Ändern von BarImpl in Foo durch Verwendung von vermieden werden interface Bar? Da sich die Signaturen und Funktionen einer Methode in BarImpl nicht ändern, muss Foo auch ohne Schnittstelle nicht geändert werden (der gesamte Zweck der Schnittstelle). Ich spreche über das Szenario, in dem nur eine Klasse BarImplBar implementiert. Für Szenarien mit mehreren Klassen verstehe ich das Prinzip der Abhängigkeitsinversion und wie es für die Benutzeroberfläche hilfreich ist.
Shishir Gupta
101

Schnittstellen sind dazu bestimmt, ein Verhalten zu definieren, dh eine Reihe von Prototypen von Funktionen / Methoden. Die Typen, die die Schnittstelle implementieren, implementieren dieses Verhalten. Wenn Sie sich also mit einem solchen Typ befassen, wissen Sie (teilweise), welches Verhalten er hat.

Es ist nicht erforderlich, eine Schnittstelle zu definieren, wenn Sie wissen, dass das von ihr definierte Verhalten nur einmal verwendet wird. KISS (halte es einfach, dumm)

superM
quelle
Nicht immer können Sie die Schnittstelle in einer Klasse verwenden, wenn diese Klasse eine generische Klasse ist.
John Isaiah Carmona
Darüber hinaus können Sie in der modernen IDE mit einem Refactoring "Schnittstelle extrahieren" eine Schnittstelle einführen, wenn diese endlich benötigt wird.
Hans-Peter Störr
Stellen Sie sich eine Utility-Klasse vor, in der es nur öffentliche statische Methoden und einen privaten Konstruktor gibt - absolut akzeptabel und von Autoren wie Joshua Bloch et al. Beworben.
Darrell Teague
63

Während Sie theoretisch keine Schnittstelle haben sollten, nur um eine Schnittstelle zu haben, gibt die Antwort von Yannis Rizos Hinweise auf weitere Komplikationen:

Wenn Sie Unit-Tests schreiben und Mock-Frameworks wie Moq oder FakeItEasy verwenden (um die beiden zuletzt verwendeten zu nennen), erstellen Sie implizit eine weitere Klasse, die die Schnittstelle implementiert. Das Durchsuchen des Codes oder die statische Analyse könnte behaupten, dass es nur eine Implementierung gibt, aber tatsächlich gibt es die interne Scheinimplementierung. Wann immer Sie anfangen, Mocks zu schreiben, werden Sie feststellen, dass das Extrahieren von Interfaces Sinn macht.

Aber warte, es gibt noch mehr. Es gibt weitere Szenarien, in denen implizite Schnittstellenimplementierungen vorhanden sind. Beispielsweise generiert der WCF-Kommunikationsstapel von .NET einen Proxy für einen Remotedienst, der die Schnittstelle erneut implementiert.

In einer sauberen Code-Umgebung stimme ich den restlichen Antworten hier zu. Beachten Sie jedoch alle Frameworks, Muster oder Abhängigkeiten, die Sie haben und die möglicherweise Schnittstellen verwenden.

Avner Shahar-Kashtan
quelle
2
+1: Ich bin nicht sicher, ob YAGNI hier zutrifft, da eine Schnittstelle und die Verwendung von Frameworks (z. B. JMock usw.), die Schnittstellen verwenden, tatsächlich Zeit sparen können.
Deco
4
@Deco: Gute Mocking-Frameworks (unter anderem JMock) benötigen nicht einmal Schnittstellen.
Michael Borgwardt
12
Das Erstellen einer Schnittstelle nur aufgrund einer Einschränkung Ihres spöttischen Frameworks scheint mir ein schrecklicher Grund zu sein. Die Verwendung von EasyMock zum Verspotten einer Klasse ist sowieso so einfach wie eine Benutzeroberfläche.
Alb
8
Ich würde sagen, es ist umgekehrt. Wenn Sie beim Testen Scheinobjekte verwenden, müssen Sie per Definition alternative Implementierungen für Ihre Schnittstellen erstellen. Dies gilt unabhängig davon, ob Sie Ihre eigenen FakeImplementation-Klassen erstellen oder sich von spöttischen Frameworks anstrengen lassen. Möglicherweise gibt es einige Frameworks wie EasyMock, die verschiedene Hacks und Tricks auf niedriger Ebene verwenden, um konkrete Klassen zu verspotten - und mehr Power für sie! - aber konzeptionell sind Mock-Objekte alternative Implementierungen zu einem Vertrag.
Avner Shahar-Kashtan
1
Sie werden die Tests in der Produktion nicht abfeuern. Warum braucht ein Mock für eine Klasse, die keine Schnittstelle benötigt, eine Schnittstelle?
Erik Reppen
32

Nein, Sie brauchen sie nicht, und ich halte es für ein Anti-Pattern, automatisch Schnittstellen für jede Klassenreferenz zu erstellen.

Die Erstellung von Foo / FooImpl für alles ist mit echten Kosten verbunden. Die IDE kann die Schnittstelle / Implementierung kostenlos erstellen, aber wenn Sie im Code navigieren, haben Sie die zusätzliche kognitive Belastung von F3 / F12 foo.doSomething(), wenn Sie zur Schnittstellensignatur und nicht zur gewünschten Implementierung gelangen. Außerdem haben Sie die Unordnung von zwei Dateien anstelle von einer für alles.

Sie sollten es also nur dann tun, wenn Sie es tatsächlich für etwas brauchen.

Wenden wir uns nun den Gegenargumenten zu:

Ich benötige Interfaces für Dependency Injection Frameworks

Schnittstellen zur Unterstützung von Frameworks sind Legacy. In Java waren Schnittstellen eine Voraussetzung für dynamische Proxys (vor CGLIB). Heute braucht man es normalerweise nicht mehr. Es wird als Fortschritt und Segen für die Entwicklerproduktivität angesehen, dass Sie sie in EJB3, Spring usw. nicht mehr benötigen.

Ich brauche Mocks für Unit-Tests

Wenn Sie Ihre eigenen Mocks schreiben und zwei aktuelle Implementierungen haben, ist eine Schnittstelle angemessen. Wir hätten diese Diskussion wahrscheinlich gar nicht erst, wenn Ihre Codebasis sowohl ein FooImpl als auch ein TestFoo hätte.

Wenn Sie jedoch ein Mocking-Framework wie Moq, EasyMock oder Mockito verwenden, können Sie Klassen verspotten und benötigen keine Schnittstellen. Dies entspricht der Einstellung foo.method = mockImplementationin einer dynamischen Sprache, in der Methoden zugewiesen werden können.

Wir brauchen Schnittstellen, die dem Dependency Inversion Principle (DIP) folgen

DIP sagt, Sie bauen auf Verträge (Schnittstellen) und nicht auf Implementierungen. Aber eine Klasse ist schon ein Vertrag und eine Abstraktion. Dafür sind die öffentlichen / privaten Schlüsselwörter gedacht. In der Universität war das kanonische Beispiel so etwas wie eine Matrix- oder Polynomklasse - Verbraucher haben eine öffentliche API, um Matrizen zu erstellen, hinzuzufügen usw., aber es ist ihnen egal, ob die Matrix in spärlicher oder dichter Form implementiert ist. Es war weder IMatrix noch MatrixImpl erforderlich, um diesen Punkt zu beweisen.

Außerdem wird DIP häufig auf allen Klassen- / Methodenaufrufebenen und nicht nur an wichtigen Modulgrenzen übermäßig angewendet. Ein Anzeichen dafür, dass Sie DIP zu häufig anwenden, ist, dass sich Ihre Schnittstelle und Implementierung im Gleichschritt ändern, sodass Sie zwei Dateien berühren müssen, um eine Änderung anstelle einer vorzunehmen. Wenn DIP richtig angewendet wird, bedeutet dies, dass sich Ihre Schnittstelle nicht oft ändern muss. Ein weiteres Anzeichen ist, dass Ihre Schnittstelle nur einen echten Verbraucher (eine eigene Anwendung) hat. Eine andere Geschichte, wenn Sie eine Klassenbibliothek für den Konsum in vielen verschiedenen Apps erstellen.

Dies ist eine Folge von Onkel Bob Martins Äußerungen über das Verspotten - Sie sollten sich nur über wichtige architektonische Grenzen lustig machen müssen. In einer Webanwendung sind der HTTP- und der DB-Zugriff die Hauptgrenzen. Alle Klassen- / Methodenaufrufe dazwischen sind es nicht. Gleiches gilt für DIP.

Siehe auch:

Wrschneider
quelle
4
Das Verspotten von Klassen anstelle von Interfaces (zumindest in Java und C #) sollte als veraltet betrachtet werden, da es keine Möglichkeit gibt, die Ausführung des Superklassenkonstruktors zu verhindern, was dazu führen kann, dass Ihr Verspottungsobjekt auf unbeabsichtigte Weise mit der Umgebung interagiert. Das Verspotten einer Schnittstelle ist sicherer und einfacher, da Sie nicht über Konstruktorcode nachdenken müssen.
Jules
4
Ich habe keine Probleme mit spöttischen Klassen, aber ich war frustriert darüber, dass die IDE-Navigation nicht wie erwartet funktioniert. Ein echtes Problem zu lösen, schlägt ein hypothetisches.
Wrschneider
1
@Jules Sie können eine konkrete Klasse einschließlich ihres Konstruktors in Java verspotten.
Assylias
1
@assylias Wie verhindern Sie, dass der Konstruktor ausgeführt wird?
Jules
2
@Jules Es hängt von Ihrem Mock-Framework ab - mit jmockit können Sie beispielsweise einfach schreiben new Mockup<YourClass>() {}und die gesamte Klasse, einschließlich ihrer Konstruktoren, wird verspottet, unabhängig davon, ob es sich um eine Schnittstelle, eine abstrakte Klasse oder eine konkrete Klasse handelt. Sie können das Konstruktorverhalten auch "überschreiben", wenn Sie dies möchten. Ich nehme an, es gibt in Mockito oder Powermock gleichwertige Möglichkeiten.
Assylias
22

Es sieht so aus, als könnten die Antworten auf beiden Seiten des Zauns folgendermaßen zusammengefasst werden:

Entwerfen Sie gut und platzieren Sie Schnittstellen dort, wo sie benötigt werden.

Wie ich in meiner Antwort auf Yannis Antwort bemerkt habe , glaube ich nicht, dass Sie jemals eine harte und schnelle Regel über Schnittstellen haben können. Die Regel muss per Definition flexibel sein. Meine Regel für Schnittstellen lautet, dass eine Schnittstelle überall dort verwendet werden sollte, wo Sie eine API erstellen. Und eine API sollte überall dort erstellt werden, wo Sie die Grenze von einem Verantwortungsbereich in einen anderen überschreiten.

Nehmen wir zum Beispiel an, Sie bauen eine CarKlasse. In Ihrer Klasse benötigen Sie auf jeden Fall eine UI-Ebene. In diesem speziellen Beispiel, nimmt es die Form eines IginitionSwitch, SteeringWheel, GearShift, GasPedal, und BrakePedal. Da dieses Auto eine enthält AutomaticTransmission, brauchen Sie keine ClutchPedal. (Und da es sich um ein schreckliches Auto handelt, gibt es keine Klimaanlage, kein Radio und keinen Sitz. Tatsächlich fehlen auch die Dielenbretter - man muss sich nur an das Lenkrad klammern und auf das Beste hoffen!)

Welche dieser Klassen benötigen also eine Schnittstelle? Die Antwort könnte alle oder keine sein - je nach Ihrem Design.

Sie könnten eine Schnittstelle haben, die so aussieht:

Interface ICabin
    Event IgnitionSwitchTurnedOn()
    Event IgnitionSwitchTurnedOff()
    Event BrakePedalPositionChanged(int percent)
    Event GasPedalPositionChanged(int percent)
    Event GearShiftGearChanged(int gearNum)
    Event SteeringWheelTurned(float degree)
End Interface

Zu diesem Zeitpunkt wird das Verhalten dieser Klassen Teil der ICabin-Schnittstelle / API. In diesem Beispiel sind die Klassen (falls vorhanden) wahrscheinlich einfach, mit einigen Eigenschaften und einer oder zwei Funktionen. Was Sie implizit mit Ihrem Design angeben, ist, dass diese Klassen nur existieren, um die konkrete Implementierung von ICabin zu unterstützen, die Sie haben, und sie können nicht für sich existieren, oder sie sind außerhalb des ICabin-Kontexts bedeutungslos.

Es ist der gleiche Grund, warum Sie private Mitglieder nicht einem Komponententest unterziehen - sie dienen nur zur Unterstützung der öffentlichen API, und daher sollte ihr Verhalten durch Testen der API getestet werden.

Wenn Ihre Klasse also nur zur Unterstützung einer anderen Klasse vorhanden ist und Sie sie konzeptionell als nicht wirklich eine eigene Domäne aufweisend betrachten, ist es in Ordnung, die Schnittstelle zu überspringen. Wenn Ihre Klasse jedoch so wichtig ist, dass Sie der Meinung sind, dass sie erwachsen genug ist, um eine eigene Domäne zu haben, geben Sie ihr eine Schnittstelle.


BEARBEITEN:

Häufig (einschließlich dieser Antwort) lesen Sie Dinge wie "Domain", "Abhängigkeit" (häufig in Verbindung mit "Injection"), die für Sie zu Beginn des Programmierens keine Bedeutung haben (das haben sie sicher nicht) mir etwas bedeuten). Für die Domain bedeutet dies genau, wie es sich anhört:

Das Gebiet, über das Herrschaft oder Autorität ausgeübt wird; die Besitztümer eines Souveräns oder Commonwealth oder dergleichen. Auch im übertragenen Sinne verwendet. [WordNet sense 2] [1913 Webster]

In den Begriffen meines Beispiels - lassen Sie uns das betrachten IgnitionSwitch. In einem Meatspace-Auto ist der Zündschalter verantwortlich für:

  1. Benutzer authentifizieren (nicht identifizieren) (sie benötigen den richtigen Schlüssel)
  2. Versorgen des Anlassers mit Strom, damit er das Auto tatsächlich starten kann
  3. Das Zündsystem mit Strom versorgen, damit es weiterarbeiten kann
  4. Strom abschalten, damit das Auto anhält.
  5. Abhängig davon, wie Sie es sehen, gibt es in den meisten (allen?) Neueren Autos einen Schalter, der verhindert, dass der Schlüssel aus der Zündung gezogen wird, während das Getriebe nicht im Park ist. Dies könnte also Teil seiner Domäne sein. (Eigentlich bedeutet es, dass ich mein System überdenken und neu gestalten muss ...)

Diese Eigenschaften bilden die Domäne des IgnitionSwitchWissens und der Verantwortlichen.

Der IgnitionSwitchist nicht verantwortlich für die GasPedal. Der Zündschalter kennt das Gaspedal in keiner Weise. Sie arbeiten beide völlig unabhängig voneinander (obwohl ein Auto ohne sie beide ziemlich wertlos wäre!).

Wie ich ursprünglich sagte, hängt es von Ihrem Design ab. Sie können eine entwerfen IgnitionSwitch, die zwei Werte hat: On ( True) und Off ( False). Sie können es auch so gestalten, dass der dafür vorgesehene Schlüssel und eine Vielzahl anderer Aktionen authentifiziert werden. Das ist der schwierige Teil, ein Entwickler zu sein, zu entscheiden, wo die Linien im Sand gezogen werden sollen - und ehrlich gesagt ist es die meiste Zeit absolut relativ. Diese Linien im Sand sind jedoch wichtig - dort befindet sich Ihre API und daher sollten sich Ihre Schnittstellen befinden.

Wayne Werner
quelle
Könnten Sie bitte näher erläutern, was Sie unter "eine eigene Domain haben" verstehen?
Lamin Sanneh
1
@LaminSanneh, ausgearbeitet. Hilft das?
Wayne Werner
8

Nein (YAGNI) , es sei denn, Sie planen, Tests für andere Klassen mit dieser Schnittstelle zu schreiben, und diese Tests würden davon profitieren, wenn Sie die Schnittstelle verspotten.

stijn
quelle
8

Von MSDN :

Schnittstellen eignen sich besser für Situationen, in denen Ihre Anwendungen viele möglicherweise nicht zusammenhängende Objekttypen benötigen, um bestimmte Funktionen bereitzustellen.

Schnittstellen sind flexibler als Basisklassen, da Sie eine einzige Implementierung definieren können, die mehrere Schnittstellen implementieren kann.

Schnittstellen sind in Situationen besser, in denen Sie die Implementierung nicht von einer Basisklasse erben müssen.

Schnittstellen sind nützlich, wenn Sie die Klassenvererbung nicht verwenden können. Beispielsweise können Strukturen nicht von Klassen erben, aber sie können Schnittstellen implementieren.

Im Allgemeinen ist es für eine einzelne Klasse nicht erforderlich, eine Schnittstelle zu implementieren. In Anbetracht der Zukunft Ihres Projekts kann es jedoch hilfreich sein, das erforderliche Verhalten von Klassen formal zu definieren.

lwm
quelle
5

Um die Frage zu beantworten: Es steckt mehr dahinter.

Ein wichtiger Aspekt einer Schnittstelle ist die Absicht.

Eine Schnittstelle ist "ein abstrakter Typ, der keine Daten enthält, aber Verhalten offenlegt" - Schnittstelle (Computing) Wenn es sich also um ein Verhalten oder eine Reihe von Verhalten handelt, die von der Klasse unterstützt werden, ist wahrscheinlich das richtige Muster für eine Schnittstelle. Wenn jedoch die Verhaltensweisen dem von der Klasse verkörperten Konzept eigen sind, möchten Sie wahrscheinlich überhaupt keine Schnittstelle.

Die erste Frage, die Sie stellen müssen, ist, welche Art von Dingen oder Prozessen Sie darstellen möchten. Folgen Sie dann den praktischen Gründen für die Implementierung dieser Art auf eine bestimmte Weise.

Joshua Drake
quelle
5

Seit Sie diese Frage gestellt haben, haben Sie vermutlich bereits die Interessen einer Schnittstelle erkannt, die mehrere Implementierungen verbirgt. Dies kann durch das Abhängigkeitsinversionsprinzip manifestiert werden.

Die Notwendigkeit einer Schnittstelle hängt jedoch nicht von der Anzahl ihrer Implementierungen ab. Die eigentliche Rolle einer Schnittstelle besteht darin, dass sie einen Vertrag definiert, der angibt, welcher Dienst bereitgestellt werden soll, anstatt wie er implementiert werden soll.

Sobald der Vertrag definiert ist, können zwei oder mehr Teams unabhängig voneinander arbeiten. Angenommen, Sie arbeiten an einem Modul A und es hängt vom Modul B ab. Die Tatsache, dass Sie eine Schnittstelle auf B erstellen, ermöglicht es Ihnen, Ihre Arbeit fortzusetzen, ohne sich um die Implementierung von B zu kümmern, da alle Details von der Schnittstelle verborgen werden. Somit wird eine verteilte Programmierung möglich.

Obwohl Modul B nur eine Implementierung seiner Schnittstelle hat, ist die Schnittstelle immer noch erforderlich.

Zusammenfassend verbirgt eine Schnittstelle Implementierungsdetails vor ihren Benutzern. Das Programmieren auf die Benutzeroberfläche hilft, mehr Dokumente zu schreiben, da der Vertrag definiert werden muss, modularere Software geschrieben werden muss, Unit-Tests gefördert und die Entwicklungsgeschwindigkeit beschleunigt werden sollen.

Hui Wang
quelle
2
Jede Klasse kann eine öffentliche Schnittstelle (die öffentlichen Methoden) und eine private Schnittstelle (die Implementierungsdetails) haben. Ich könnte argumentieren, dass die öffentliche Schnittstelle der Klasse der Vertrag ist. Sie benötigen kein zusätzliches Element, um an diesem Vertrag zu arbeiten.
Fuhrmanator
4

Alle Antworten hier sind sehr gut. Tatsächlich werden die meisten der Zeit , dass Sie nicht brauchen eine andere Schnittstelle zu implementieren. Aber es gibt Fall , in dem Sie können wollen , es trotzdem zu tun. Hier ist ein Fall, in dem ich es mache:

Die Klasse implementiert eine andere Schnittstelle, die ich nicht
häufig mit Adapterklasse verfügbar machen möchte, die Code von Drittanbietern überbrückt.

interface NameChangeListener { // Implemented by a lot of people
    void nameChanged(String name); 
} 

interface NameChangeCount { // Only implemented by my class
    int getCount();
}

class NameChangeCounter implements NameChangeListener, NameChangeCount {
    ...
}

class SomeUserInterface {
    private NameChangeCount currentCount; // Will never know that you can change the counter
}

Die Klasse verwendet eine spezielle Technologie, die nicht durchläuft,
wenn sie mit externen Bibliotheken interagiert. Selbst wenn es nur eine Implementierung gibt, verwende ich eine Schnittstelle, um sicherzustellen, dass keine unnötige Kopplung mit der externen Bibliothek hergestellt wird.

interface SomeRepository { // Guarantee that the external library details won't leak trough
    ...
}

class OracleSomeRepository implements SomeRepository { 
    ... // Oracle prefix allow us to quickly know what is going on in this class
}


Layerübergreifende Kommunikation Auch wenn nur eine UI-Klasse jemals eine der Domänenklassen implementiert, ermöglicht sie eine bessere Trennung zwischen diesen Layern und vermeidet vor allem zyklische Abhängigkeiten.

package project.domain;

interface UserRequestSource {
    public UserRequest getLastRequest();
}

class UserBehaviorAnalyser {
    private UserRequestSource requestSource;
}

package project.ui;

class OrderCompleteDialog extends SomeUIClass implements project.domain.UserRequestSource {
    // UI concern, no need for my domain object to know about this method.
    public void displayLabelInErrorMode(); 

    // They most certainly need to know about *that* though
    public UserRequest getLastRequest();
}

Für die meisten Objekte sollte nur eine Teilmenge der Methode verfügbar sein. Tritt
meistens auf, wenn ich eine Konfigurationsmethode für die konkrete Klasse habe

interface Sender {
    void sendMessage(Message message)
}

class PacketSender implements Sender {
    void sendMessage(Message message);
    void setPacketSize(int sizeInByte);
}

class Throttler { // This class need to have full access to the object
    private PacketSender sender;

    public useLowNetworkUsageMode() {
        sender.setPacketSize(LOW_PACKET_SIZE);
        sender.sendMessage(new NotifyLowNetworkUsageMessage());

        ... // Other details
    }
}

class MailOrder { // Not this one though
    private Sender sender;
}

Letztendlich benutze ich die Schnittstelle aus dem gleichen Grund, aus dem ich das private Feld benutze: Ein anderes Objekt sollte keinen Zugriff auf Dinge haben, auf die es keinen Zugriff haben sollte. Wenn ich einen solchen Fall habe, führe ich eine Schnittstelle ein, auch wenn nur eine Klasse sie implementiert.

Laurent Bourgault-Roy
quelle
2

Schnittstellen sind wirklich wichtig, aber versuchen Sie, die Anzahl der vorhandenen Schnittstellen zu kontrollieren.

Nachdem man die Entwicklung von Schnittstellen für so gut wie alles hinter sich gelassen hat, kann man leicht mit zerhacktem Spaghetti-Code enden. Ich verweise auf die größere Weisheit von Ayende Rahien, der einige sehr weise Worte zu diesem Thema geschrieben hat:

http://ayende.com/blog/153889/limit-your-abstractions-analyzing-a-ddd-application

Dies ist sein erster Beitrag in einer ganzen Reihe, also lies weiter!

Lord Spa
quelle
'zerstückelten Spaghetti' Code wird auch als Ravioli - Code bekannt c2.com/cgi/wiki?RavioliCode
CurtainDog
Klingt für mich eher nach Lasagne-Code oder Baklava-Code - Code mit zu vielen Ebenen. ;-)
dodgy_coder
2

Ein Grund, warum Sie in diesem Fall immer noch eine Schnittstelle einführen möchten, ist das Befolgen des Abhängigkeitsinversionsprinzips . Das heißt, das Modul, das die Klasse verwendet, hängt von ihrer Abstraktion (dh der Schnittstelle) ab, anstatt von einer konkreten Implementierung. Es entkoppelt Komponenten auf hoher Ebene von Komponenten auf niedriger Ebene.

dodgy_coder
quelle
2

Es gibt keinen wirklichen Grund, etwas zu tun. Schnittstellen sollen Ihnen helfen und nicht das Ausgabeprogramm. Selbst wenn das Interface von einer Million Klassen implementiert wird, gibt es keine Regel, die besagt, dass Sie eine erstellen müssen. Sie erstellen eine, damit Sie oder jeder andere, der Ihren Code verwendet, etwas ändern möchten, das in alle Implementierungen einfließt. Das Erstellen einer Schnittstelle unterstützt Sie in allen zukünftigen Fällen, in denen Sie möglicherweise eine andere Klasse erstellen möchten, die diese implementiert.

John Demetriou
quelle
1

Es ist nicht immer erforderlich, eine Schnittstelle für eine Klasse zu definieren.

Einfache Objekte wie Wertobjekte sind nicht mehrfach implementiert. Sie müssen auch nicht verspottet werden. Die Implementierung kann alleine getestet werden, und wenn andere Klassen getestet werden, die davon abhängen, kann das tatsächliche Wertobjekt verwendet werden.

Denken Sie daran, dass das Erstellen einer Schnittstelle mit Kosten verbunden ist. Es muss während der Implementierung aktualisiert werden, es benötigt eine zusätzliche Datei, und einige IDEs haben Probleme beim Zoomen in der Implementierung, nicht in der Benutzeroberfläche.

Daher würde ich Interfaces nur für übergeordnete Klassen definieren, bei denen Sie eine Abstraktion von der Implementierung wünschen.

Beachten Sie, dass Sie mit einer Klasse ein kostenloses Interface erhalten. Neben der Implementierung definiert eine Klasse eine Schnittstelle aus der Menge der öffentlichen Methoden. Diese Schnittstelle wird von allen abgeleiteten Klassen implementiert. Eigentlich ist es keine Schnittstelle, aber es kann genauso verwendet werden. Ich glaube nicht, dass es notwendig ist, eine Schnittstelle neu zu erstellen, die bereits unter dem Namen der Klasse existiert.

Florian F
quelle