Sollten Schnittstellen in einem separaten Paket platziert werden? [geschlossen]

80

Ich bin neu in einem Team, das an einem ziemlich großen Projekt mit vielen Komponenten und Abhängigkeiten arbeitet. Für jede Komponente gibt es eineinterfaces Paket, in dem die verfügbaren Schnittstellen für diese Komponente platziert sind. Ist das eine gute Praxis?

Meine übliche Praxis war immer, dass Schnittstellen und Implementierungen im selben Paket enthalten sind.

kctang
quelle
4
Warum ist dies mit [sprachunabhängig] gekennzeichnet?
Finnw
Eine andere Situation, in der dies bevorzugt werden könnte, wenn das Modul über einen RPC anstatt über einen direkten Aufruf kommuniziert werden soll - dann ist die Schnittstelle nützlich, um Proxys für den Client zu generieren?
Redzedi

Antworten:

101

Das Platzieren sowohl der Schnittstelle als auch der Implementierung ist an der Tagesordnung und scheint kein Problem zu sein.

Nehmen wir zum Beispiel die Java-API - die meisten Klassen haben beide Schnittstellen und ihre Implementierungen im selben Paket.

Nehmen Sie zum Beispiel das java.utilPaket:

Es enthält die Schnittstellen wie Set, Map, List, während auch die Implementierungen, die wie HashSet, HashMapund ArrayList.

Darüber hinaus sind die Javadocs so konzipiert, dass sie unter diesen Bedingungen gut funktionieren, da die Dokumentation bei der Anzeige des Paketinhalts in die Ansichten Schnittstellen und Klassen unterteilt wird .

Pakete nur für Schnittstellen zu haben, kann tatsächlich etwas übertrieben sein, es sei denn, es gibt eine enorme Anzahl von Schnittstellen. Aber die Schnittstellen in ihre eigenen Pakete zu trennen, nur um dies zu tun, klingt nach schlechter Praxis.

Wenn es notwendig ist, den Namen einer Schnittstelle von einer Implementierung zu unterscheiden, kann eine Namenskonvention verwendet werden, um die Identifizierung von Schnittstellen zu vereinfachen:

  • Stellen Sie dem Schnittstellennamen ein vor I. Dieser Ansatz wird mit den Schnittstellen im .NET Framework verfolgt. Es wäre ziemlich einfach zu sagen, dass dies IListeine Schnittstelle für eine Liste ist.

  • Verwenden Sie das ableSuffix - . Dieser Ansatz wird häufig in der Java-API verwendet, z. B. Comparableund Iterable, Serializableum nur einige zu nennen.

Coobird
quelle
43
+1, obwohl ich die I * -Namenskonvention nur empfehlen würde, wenn Sie sich in der .NETverse befinden, und selbst dann nur aus
Gründen der
7
Ich habe diese Konvention in Java-Code verwendet und fand sie nützlich .... YMMV
hhafez
5
Ich weiß, dass dies etwas alt ist, aber was ich jetzt vorgeschlagen habe, ist, die Schnittstelle klar zu benennen und die Implementierung etwas strenger zu benennen. zB Schnittstelle = Store-Implementierung = StoreImpl Hauptsächlich, um hervorzuheben, dass die Schnittstelle über die Implementierung verwendet werden soll.
frostymarvelous
1
Ich denke, dass dieser Ansatz, auch wenn er richtig aussieht, zu architektonischen Problemen führt. Beispielsweise gibt es keine Möglichkeit, Bibliotheken zu definieren, die nur die Schnittstellen verwenden, und diese dann in einer gemeinsamen Bibliothek, dh der Infrastrukturschicht, zu implementieren.
Luca Masera
1
Ich stimme dem Kommentar zu, dass das Präfix "I" nicht für Schnittstellen verwendet wird, wie ich es nicht empfehlen würde, das Suffix "impl" für Implementierungen zu verwenden. Dies ist ein schöner Artikel, der sich mit diesen Fällen befasst: octoperf.com/blog/2016 / 10/27 / impl-Klassen-sind-böse
raspacorp
19

Für jede Sprache ist es in Ordnung, sie in demselben Paket zusammenzustellen. Wichtig ist, was der Außenwelt ausgesetzt ist und wie sie von außen aussieht. Niemand wird wissen oder sich darum kümmern, ob sich die Implementierung in demselben Paket befindet oder nicht.

Schauen wir uns diese spezielle Instanz an.

Wenn Sie alle öffentlichen Dinge in einem Paket und private Dinge in einem anderen Paket haben, das nicht öffentlich verfügbar ist, sieht der Client der Bibliothek ein Paket. Wenn Sie die privaten Dinge mit den öffentlich zugänglichen Dingen in das Paket verschieben, sie jedoch nicht aus dem Paket heraus verfügbar machen, sieht der Client genau dasselbe.

Dies riecht also nach einer Regel ohne guten Grund: Es trifft eine Entscheidung, die darauf basiert, dass etwas öffentlich sichtbar ist, ohne dass diese Entscheidung Auswirkungen auf das hat, was öffentlich sichtbar ist.

Das heißt, wenn es in einem bestimmten Fall eine gute Idee ist, die Schnittstelle und die Implementierung in separate Pakete aufzuteilen, fahren Sie fort und tun Sie dies. Gründe dafür sind, dass das Paket riesig ist oder Sie eine alternative Implementierung haben, die Sie möglicherweise anstelle der Standardimplementierung verknüpfen möchten.

cjs
quelle
Angesichts des 'Java'-Tags in der Frage - was bedeutet ein' öffentlich verfügbares Paket 'in Java? Oder ist Ihre Antwort nicht Java-spezifisch? Ich dachte, nur Paketmitglieder (wie Klassen, Schnittstellen und ihre Mitglieder), nicht Pakete selbst, hätten Zugriffssteuerungsspezifizierer wie 'öffentlich', 'privat' usw. (Dies wird unter @ stack stackoverflow.com/questions/4388715/… weiter unten erläutert. )
Bacar
Ich habe gerade den Wortlaut klargestellt; Mit "öffentlich exponiertes Paket" meinte ich wirklich "das Paket mit den öffentlich exponierten Dingen".
cjs
Sicherlich sollten alle Pakete öffentliche Dinge haben; Wenn nicht, wie können Sie das Paket verwenden?
Bacar
In diesem speziellen Fall gab es Pakete mit nichts Öffentlichem, die jedoch von Paketen verwendet wurden, die Dinge öffentlich machten. Ersteres kann also nicht direkt, sondern nur indirekt über Letzteres verwendet werden.
cjs
1
Aber die gleiche Frage gilt. Wie können die Pakete, die Dinge öffentlich gemacht haben, die Pakete verwenden, die nichts öffentlich gemacht haben? Ich kenne in Java keine Möglichkeit, "das erstere kann nicht direkt, sondern nur indirekt durch das letztere verwendet werden". Wenn Ihr Implementierungspaket nur private Mitglieder hat, ist Ihr Schnittstellenpaket genauso unzugänglich wie der Client. & ähnlich für andere Zugriffsbereiche. In Java gibt es kein Äquivalent zu friend(C ++) oder internal(C #).
Bacar
10

In vielen Frameworks wie OSGi müssen Sie fast. Ich denke, dies fördert eine lockerere Kopplung auf der Verpackung anstelle der Glasebene.

javamonkey79
quelle
7

Ein Argument für das Einfügen von Schnittstellen in verschiedene Pakete ist, dass es einfacher ist, API-Jars zu erstellen, die an Verbraucher Ihres Produkts oder Ihrer Dienstleistung verteilt werden können. Es ist durchaus möglich, dies mit Schnittstellen und Implementierungen zusammen zu tun, aber einfacher zu skripten, wenn sie sich in verschiedenen Paketen befinden.

Cameron Pope
quelle
7
Sie können das API-Projekt separat (als separate verteilbare Datei) festlegen, sodass Schnittstellen und Implementierung nicht im selben Paket enthalten sind. Ein bisschen wie Sie haben Unit-Tests im selben Paket wie getestete Klassen, aber immer noch in einem separaten Build-Artefakt. Gläser und Pakete sind in Java ziemlich orthogonal.
George
7

Das Buch Practical Software Engineering: Ein Fallstudienansatz befürwortet das Einfügen von Schnittstellen in separate Projekte / Pakete.

Die PCMEF + -Architektur, über die das Buch spricht, hat die folgenden Prinzipien:

  1. Prinzip der Abwärtsabhängigkeit (DDP)
  2. Upward Notification Principle (UNP)
  3. Prinzip der Nachbarschaftskommunikation (NCP)
  4. Explizites Assoziationsprinzip (EAP)
  5. Cycle Elimination Principle (CEP)
  6. Klassennamenprinzip (CNP)
  7. Prinzip des Bekanntschaftspakets (APP)

Die Beschreibung der Prinzipien 3 und 7 erklärt, warum dies eine gute Idee ist:

Das Nachbarkommunikationsprinzip verlangt, dass ein Paket nur direkt mit seinem Nachbarpaket kommunizieren kann. Dieses Prinzip stellt sicher, dass das System nicht in ein inkompressibles Netzwerk von miteinander kommunizierenden Objekten zerfällt. Um dieses Prinzip durchzusetzen, wird für die Nachrichtenübertragung zwischen nicht benachbarten Objekten eine Delegierung verwendet (Abschnitt 9.1.5.1). In komplexeren Szenarien kann das Bekanntschaftspaket (Abschnitt 9.1.8.2) zum Gruppieren von Schnittstellen verwendet werden, um die Zusammenarbeit bei entfernten Paketen zu unterstützen.

Das Prinzip des Bekanntschaftspakets ist die Konsequenz des Prinzips der Nachbarschaftskommunikation. Das Bekanntschaftspaket besteht aus Schnittstellen, die ein Objekt anstelle konkreter Objekte in Argumenten an Methodenaufrufe übergibt. Die Schnittstellen können in jedem PCMEF-Paket implementiert werden. Dies ermöglicht effektiv die Kommunikation zwischen nicht benachbarten Paketen, während das Abhängigkeitsmanagement auf ein einziges Bekanntschaftspaket zentralisiert wird. Die Notwendigkeit eines Bekanntschaftspakets wurde in Abschnitt 9.1.8.2 erläutert und wird als nächstes im PCMEF-Kontext erneut erörtert.

Siehe diesen Link: http://comp.mq.edu.au/books/pse/about_book/Ch9.pdf

Kenci
quelle
5

Ja, dies ist eine sehr gute Vorgehensweise, da Sie damit die Benutzeroberfläche veröffentlichen können, ohne Ihre spezifische Implementierung zu veröffentlichen. Wenn Sie jedoch keine externen Schnittstellen veröffentlichen müssen, können Sie die Schnittstellendefinitionen problemlos in dasselbe Paket wie die Implementierung einfügen.

Paul Sonier
quelle
2
Wenn Sie Schnittstellen für das JCP definieren oder etwas, das sinnvoll ist, aber wenn Sie mehrere konkrete Implementierungen dieser Schnittstellen für Ihr Projekt erstellen, scheint es eine unnötige Belastung zu sein, die Schnittstellen separat zu verpacken.
Brian Reiter
2
Es ist auf jeden Fall keine große Belastung ...
Paul Sonier
4
-1 Ja, das ist es. Es macht es unnötig schwierig, die Implementierungen zu finden und Dinge zu trennen, die zusammen gehören.
Michael Borgwardt
5
Das Nichtveröffentlichen der Implementierungen ist eine völlig andere Angelegenheit, die durch paketprivates Erstellen erfolgen kann - und NICHT durch Platzieren in einem separaten Paket. Es gibt kein nicht öffentliches Paket.
Michael Borgwardt
1
Darüber hinaus klingt es so, als würden alle Schnittstellen unnötig öffentlich gemacht.
Adeel Ansari
4

Wir tun dies dort, wo ich arbeite (dh: Schnittstelle in ein Paket und Implementierung in ein anderes) und der Hauptvorteil, den wir daraus ziehen, ist, dass wir leicht zwischen Implementierungen wechseln können.

hhafez
quelle
7
Wie hilft es Ihnen, die Implementierungen zu wechseln?
Janusz
siehe die Antwort von Cameron Pope, es ist genau der Grund, warum wir es hier tun
hhafez
3

Ich habe nicht viel Java-Erfahrung, aber ich mache dies gerne als bewährte Methode in C # /. NET, da dies eine zukünftige Erweiterung ermöglicht, bei der die Assemblys mit den konkreten Klassen, die die Schnittstellen implementieren, möglicherweise nicht bis zum Ende verteilt sind Client, weil sie von einer Zwischenhändlerfabrik oder in einem Webdienstszenario über das Kabel übertragen werden.

Jesse C. Slicer
quelle
2

Normalerweise setze ich Schnittstellen in die Implementierung ein, aber ich kann irgendwie sehen, warum Sie sie möglicherweise getrennt halten möchten. Angenommen, jemand wollte Klassen basierend auf Ihren Schnittstellen neu implementieren, er würde ein jar / lib / etc für Ihre Implementierung benötigen und nicht nur die Schnittstellen. Wenn sie getrennt sind, können Sie einfach "Hier ist meine Implementierung dieser Schnittstelle" sagen und damit fertig sein. Wie ich schon sagte, nicht was ich tue, aber ich kann irgendwie sehen, warum manche es wollen.

Matt_JD
quelle
2

Ich mache das für ein Projekt und es funktioniert aus folgenden Gründen gut für mich:

  • Separat verpackte Schnittstellen und Implementierungen gelten für einen anderen Anwendungsfall als für die verschiedenen Map- oder Set-Typen. Es gibt keinen Grund, ein Paket nur für Bäume zu haben (java.util.tree.Map, java.util.tree.Set). Es ist nur eine Standard-Datenstruktur, also stellen Sie sie mit den anderen Datenstrukturen zusammen. Wenn Sie es jedoch mit einem Puzzlespiel zu tun haben, das eine wirklich einfache Debug-Oberfläche und eine hübsche Produktionsoberfläche hat, haben Sie als Teil des Frontends möglicherweise einen com.your.app.skin.debug und einen com.your.app .skin.pretty. Ich würde diese nicht in dasselbe Paket einfügen, da sie unterschiedliche Aufgaben ausführen und ich weiß, dass ich auf SmurfNamingConvention (DebugAttackSurface, DebugDefenceSurface, PrettyAttackSurface usw.) zurückgreifen würde, um einen informellen Namespace für die beiden zu erstellen, wenn sie sich im selben Paket befinden.

  • Meine Problemumgehung für das Problem, verwandte Schnittstellen und Implementierungen in separaten Paketen zu finden, besteht darin, eine Namenskonfiguration für meine Pakete zu übernehmen. Beispielsweise kann ich alle meine Schnittstellen in com.your.app.skin.framework haben und weiß, dass andere Pakete auf derselben Ebene des Paketbaums Implementierungen sind. Der Nachteil ist, dass dies eine unkonventionelle Konvention ist. Ehrlich gesagt werde ich sehen, wie gut diese Konvention in 6 Monaten ist :)

  • Ich benutze diese Technik nicht religiös. Es gibt Schnittstellen, die nur in einer bestimmten Implementierung sinnvoll sind. Ich stecke sie nicht in das Framework-Paket. Es gibt einige Pakete, in denen es nicht so aussieht, als würde ich 40 verschiedene Implementierungsklassen erstellen, also kümmere ich mich nicht darum.

  • Meine Anwendung verwendet Guice und hat sehr viele Schnittstellen.

Fragen der Programmgestaltung haben im Allgemeinen Vor- und Nachteile, und es gibt keine einheitliche Antwort. Zum Beispiel, warum würden Sie diese Technik jemals in einem winzigen 200-Zeilen-Programm verwenden? Angesichts meiner anderen architektonischen Entscheidungen ist es für mich sinnvoll, mein spezifisches Problem zu lösen. :) :)

Moray Gelbsucht
quelle
1

Ich bevorzuge sie im gleichen Paket. Es ist sinnlos, ein Paket speziell für Schnittstellen zu erstellen. Dies ist besonders redundant für Teams, die ihre Schnittstellenpräfixe und -suffixe einfach lieben (z. B. "I" und "Impl" für Java).

Wenn eine Reihe von Schnittstellen als öffentliche API veröffentlicht werden muss, ist es sinnvoller, sie in einem völlig separaten Projekt zu belassen und stattdessen Projektabhängigkeiten zu erstellen. Aber es läuft alles auf die Präferenz und Bequemlichkeit der Situation hinaus, nehme ich an.

aberrant80
quelle
0

Legen Sie sie in Pakete, die Ihre Projekte widerspiegeln. Es ist in Ordnung und üblich, Schnittstellen und Implementierungen zusammenzustellen, wenn sie Teil desselben Projekts sind. Wenn Sie jedoch eine API schreiben, wählt wahrscheinlich jemand anderes einen für das Projekt relevanten Paketnamen.

Wenn es sich um dasselbe Projekt handelt, sehe ich im Allgemeinen keinen Vorteil darin, die Schnittstellen in einem von ihren Implementierungen getrennten Paket zu halten. Wenn es unübersichtlich wird, kann es unabhängig von der Schnittstellenanordnung zu anderen Problemen bei der Paketbenennung kommen.

Rog
quelle
0

Ich denke, es ist wichtig zu beachten, dass es für das OSGi-Framework schöner ist, dass sie sich in verschiedenen Paketen befinden, sodass Sie problemlos ein ganzes Paket exportieren können, während Sie die Implementierungspakete ausblenden.

jkabugu
quelle
-1

Es gibt viele gute Gründe, Schnittstellen in einem von der Implementierung getrennten Paket zusammenzufassen. Beispielsweise möchten Sie möglicherweise einen Container verwenden, der Dependency Injection unterstützt , um die erforderlichen Implementierungen zur Laufzeit zu verkabeln. In diesem Fall muss nur die Schnittstelle zur Erstellungszeit vorhanden sein und die Implementierung kann zur Laufzeit bereitgestellt werden. Alternativ möchten Sie möglicherweise mehrere Implementierungen (z. B. die Verwendung von Scheinimplementierungen zum Testen) oder mehrere Versionen einer bestimmten Implementierung zur Laufzeit (z. B. für A / B-Tests usw.) bereitstellen . Für diese Art von Anwendungsfällen ist es bequemer, die Schnittstelle und die Implementierung separat zu verpacken.

Tyson
quelle
Sie müssen die Implementierung nicht in ein anderes Paket einfügen, um eines der von Ihnen aufgelisteten Dinge zu tun
Crowie
Das ist fair, aber ich denke immer noch, dass der Unterschied in den Anforderungen "zur Erstellungszeit gegenüber der Laufzeit vorhanden sein" ein überzeugender Fall für die separate Verpackung ist. Ich habe den Text aktualisiert, um dies deutlicher wiederzugeben.
Tyson