Welche Strategie verwenden Sie für die Paketbenennung in Java-Projekten und warum? [geschlossen]

88

Ich habe vor einiger Zeit darüber nachgedacht und es ist kürzlich wieder aufgetaucht, als mein Shop seine erste echte Java-Web-App macht.

Als Intro sehe ich zwei Hauptstrategien zur Benennung von Paketen. (Um klar zu sein, ich beziehe mich nicht auf den gesamten Teil von 'domain.company.project', ich spreche über die Paketkonvention darunter.) Wie auch immer, die Paketbenennungskonventionen, die ich sehe, lauten wie folgt:

  1. Funktional: Benennen Sie Ihre Pakete architektonisch nach ihrer Funktion und nicht nach ihrer Identität gemäß der Geschäftsdomäne. Ein anderer Begriff hierfür könnte die Benennung nach "Schicht" sein. Sie hätten also ein * .ui-Paket und ein * .domain-Paket und ein * .orm-Paket. Ihre Pakete sind eher horizontale als vertikale Scheiben.

    Dies ist weitaus häufiger als die logische Benennung. Tatsächlich glaube ich nicht, dass ich jemals ein Projekt gesehen oder gehört habe, das dies tut. Das macht mich natürlich misstrauisch (so als würde ich denken, dass Sie eine Lösung für ein NP-Problem gefunden haben), da ich nicht besonders schlau bin und davon ausgehe, dass jeder gute Gründe haben muss, es so zu machen, wie er es tut. Auf der anderen Seite bin ich nicht gegen Leute, die nur den Elefanten im Raum vermissen, und ich habe noch nie ein Argument dafür gehört , wie man Pakete so benennt. Es scheint nur der De-facto-Standard zu sein.

  2. Logisch: Benennen Sie Ihre Pakete nach ihrer Geschäftsdomänenidentität und fügen Sie jede Klasse, die mit diesem vertikalen Funktionsbereich zu tun hat, in dieses Paket ein.

    Ich habe das noch nie gesehen oder gehört, wie ich bereits erwähnt habe, aber es macht für mich eine Menge Sinn.

    1. Ich nähere mich eher vertikalen als horizontalen Systemen. Ich möchte das Auftragsabwicklungssystem entwickeln, nicht die Datenzugriffsschicht. Natürlich besteht eine gute Chance, dass ich bei der Entwicklung dieses Systems die Datenzugriffsschicht berühre, aber der Punkt ist, dass ich das nicht so sehe. Dies bedeutet natürlich, dass es schön wäre, wenn ich einen Änderungsauftrag erhalte oder eine neue Funktion implementieren möchte, nicht in einer Reihe von Paketen herumfischen zu müssen, um alle zugehörigen Klassen zu finden. Stattdessen schaue ich einfach in das X-Paket, weil das, was ich tue, mit X zu tun hat.

    2. Unter Entwicklungsgesichtspunkten sehe ich es als großen Vorteil an, dass Ihre Pakete eher Ihre Geschäftsdomäne als Ihre Architektur dokumentieren. Ich bin der Meinung, dass die Domäne fast immer der Teil des Systems ist, der schwieriger zu erfassen ist, da die Architektur des Systems, insbesondere zu diesem Zeitpunkt, in seiner Implementierung fast banal wird. Die Tatsache, dass ich mit dieser Art von Namenskonvention zu einem System kommen kann und sofort aus der Benennung der Pakete weiß, dass es sich um Bestellungen, Kunden, Unternehmen, Produkte usw. handelt, scheint verdammt praktisch.

    3. Dies scheint es Ihnen zu ermöglichen, die Zugriffsmodifikatoren von Java besser zu nutzen. Auf diese Weise können Sie Schnittstellen in Subsystemen viel sauberer definieren als in Schichten des Systems. Wenn Sie also ein Auftragssubsystem haben, das transparent persistent sein soll, können Sie theoretisch nie etwas anderes wissen lassen, dass es persistent ist, indem Sie keine öffentlichen Schnittstellen zu seinen Persistenzklassen in der Dao-Schicht erstellen und stattdessen die Dao-Klasse in packen müssen mit nur den Klassen, mit denen es sich befasst. Wenn Sie diese Funktionalität verfügbar machen möchten, können Sie natürlich eine Schnittstelle dafür bereitstellen oder sie öffentlich machen. Es scheint nur so, als ob Sie viel davon verlieren, wenn Sie einen vertikalen Teil der Funktionen Ihres Systems auf mehrere Pakete verteilen.

    4. Ich nehme an, ein Nachteil, den ich sehen kann, ist, dass es das Herausreißen von Schichten etwas schwieriger macht. Anstatt nur ein Paket zu löschen oder umzubenennen und dann ein neues mit einer alternativen Technologie zu entfernen, müssen Sie alle Klassen in allen Paketen ändern. Ich sehe jedoch nicht, dass dies eine große Sache ist. Es mag an mangelnder Erfahrung liegen, aber ich muss mir vorstellen, dass die Häufigkeit, mit der Sie Technologien austauschen, im Vergleich zu der Häufigkeit, mit der Sie vertikale Feature-Slices in Ihrem System bearbeiten, verblasst.

Ich denke also, die Frage würde dann an Sie gehen, wie benennen Sie Ihre Pakete und warum? Bitte haben Sie Verständnis dafür, dass ich nicht unbedingt denke, dass ich hier auf die goldene Gans oder so gestoßen bin. Ich bin ziemlich neu in all dem mit größtenteils akademischer Erfahrung. Ich kann jedoch die Löcher in meiner Argumentation nicht erkennen, also hoffe ich, dass Sie alle es können, damit ich weitermachen kann.

Tim Visher
quelle
Was ich bisher gehört habe, scheint darauf hinzudeuten, dass es sich um eine Art Urteil handelt. Die meisten Antworten haben sich jedoch nicht auf die praktischen Überlegungen konzentriert. Das ist mehr, wonach ich suche. Vielen Dank für die bisherige Hilfe!
Tim Visher
Ich denke nicht, dass es ein Urteilsspruch ist. Erst nach Ebenen, dann nach Funktionen zu teilen, ist definitiv der richtige Weg. Ich aktualisiere meine Antwort.
Eljenso
@eljenso: Sagen Sie das den Eclipse-Leuten :) In Eclipse besteht die erste Unterteilung in "Features", die Bereitstellungseinheiten und auch Funktionen sind. Innerhalb von "Feature" gibt es "Plugins", die normalerweise nach Ebenen unterteilt sind. Plugins innerhalb desselben "Features" müssen jedoch immer zusammen bereitgestellt werden. Wenn Sie nur eine einzige Bereitstellungseinheit haben, kann es jedoch sinnvoll sein, zuerst nach Schicht und erst dann nach Funktionalität zu teilen. Bei mehreren Bereitstellungseinheiten möchten Sie nicht nur die Präsentationsebene bereitstellen, sondern zusätzliche Funktionen.
Peter Štibraný

Antworten:

32

Beim Verpackungsdesign teile ich zuerst nach Ebenen und dann nach anderen Funktionen.

Es gibt einige zusätzliche Regeln:

  1. Ebenen werden von den allgemeinsten (unten) bis zu den spezifischsten (oben) gestapelt.
  2. Jede Schicht hat eine öffentliche Schnittstelle (Abstraktion)
  3. Eine Schicht kann nur von der öffentlichen Schnittstelle einer anderen Schicht abhängen (Kapselung).
  4. Eine Ebene kann nur von allgemeineren Ebenen abhängen (Abhängigkeiten von oben nach unten).
  5. Eine Schicht hängt vorzugsweise von der Schicht direkt darunter ab

So könnten Sie beispielsweise für eine Webanwendung die folgenden Ebenen in Ihrer Anwendungsebene haben (von oben nach unten):

  • Präsentationsschicht: Erzeugt die Benutzeroberfläche, die in der Clientebene angezeigt wird
  • Anwendungsschicht: Enthält anwendungsspezifische Logik, statusbehaftet
  • Service-Schicht: Gruppiert die Funktionalität nach Domäne, ohne Status
  • Integrationsschicht: Ermöglicht den Zugriff auf die Backend-Ebene (db, jms, email, ...)

Für das resultierende Paketlayout gelten folgende zusätzliche Regeln:

  • Die Wurzel jedes Paketnamens ist <prefix.company>.<appname>.<layer>
  • Die Schnittstelle einer Ebene wird durch die Funktionalität weiter aufgeteilt: <root>.<logic>
  • Der privaten Implementierung einer Ebene wird private vorangestellt: <root>.private

Hier ist ein Beispiellayout.

Die Präsentationsebene ist nach Ansichtstechnologie und optional nach (Gruppen von) Anwendungen unterteilt.

com.company.appname.presentation.internal
com.company.appname.presentation.springmvc.product
com.company.appname.presentation.servlet
...

Die Anwendungsschicht ist in Anwendungsfälle unterteilt.

com.company.appname.application.lookupproduct
com.company.appname.application.internal.lookupproduct
com.company.appname.application.editclient
com.company.appname.application.internal.editclient
...

Die Service-Schicht ist in Geschäftsdomänen unterteilt, die von der Domänenlogik in einer Backend-Schicht beeinflusst werden.

com.company.appname.service.clientservice
com.company.appname.service.internal.jmsclientservice
com.company.appname.service.internal.xmlclientservice
com.company.appname.service.productservice
...

Die Integrationsschicht ist in "Technologien" und Zugriffsobjekte unterteilt.

com.company.appname.integration.jmsgateway
com.company.appname.integration.internal.mqjmsgateway
com.company.appname.integration.productdao
com.company.appname.integration.internal.dbproductdao
com.company.appname.integration.internal.mockproductdao
...

Der Vorteil der Trennung solcher Pakete besteht darin, dass die Komplexität einfacher verwaltet werden kann und die Testbarkeit und Wiederverwendbarkeit erhöht wird. Obwohl es nach viel Aufwand aussieht, ist es meiner Erfahrung nach sehr natürlich und jeder, der an dieser Struktur (oder ähnlichem) arbeitet, nimmt es in wenigen Tagen auf.

Warum finde ich den vertikalen Ansatz nicht so gut?

In dem Schichtmodell können mehrere verschiedene übergeordnete Module dasselbe untergeordnete Modul verwenden. Beispiel: Sie können mehrere Ansichten für dieselbe Anwendung erstellen, mehrere Anwendungen können denselben Dienst verwenden, mehrere Dienste können dasselbe Gateway verwenden. Der Trick dabei ist, dass sich beim Durchlaufen der Ebenen die Funktionalität ändert. Module in spezifischeren Ebenen werden auf Modulen der allgemeineren Ebene nicht 1-1 zugeordnet, da die von ihnen ausgedrückten Funktionalitätsebenen nicht 1-1 zugeordnet werden.

Wenn Sie den vertikalen Ansatz für Package - Design verwenden, dh Sie können durch Funktionalität teilen zuerst, dann zwingen Sie alle Bausteine mit unterschiedlichen Ebenen der Funktionalität in die gleiche ‚-Funktionalität Jacke‘. Sie können Ihre allgemeinen Module für das spezifischere entwerfen. Dies verstößt jedoch gegen das wichtige Prinzip, dass die allgemeinere Schicht keine spezifischeren Schichten kennen sollte. Die Service-Schicht sollte beispielsweise nicht nach Konzepten aus der Anwendungsschicht modelliert werden.

Eljenso
quelle
"Der Vorteil der Trennung solcher Pakete besteht darin, dass die Komplexität einfacher zu verwalten ist und die Testbarkeit und Wiederverwendbarkeit erhöht wird." Sie gehen wirklich nicht auf die Gründe ein, warum dies so ist.
MangoDrunk
1
Sie gehen nicht auf die Gründe ein, warum es nicht so ist. Aber trotzdem ... die Regeln für die Organisation sind ziemlich klar und unkompliziert, so dass die Komplexität reduziert wird. Es ist aufgrund der Organisation selbst testbarer und wiederverwendbarer. Jeder kann es ausprobieren und danach können wir zustimmen oder nicht zustimmen. Ich erkläre kurz einige der Nachteile des vertikalen Ansatzes und gebe implizit Gründe an, warum ich den horizontalen Ansatz für einfacher halte.
Eljenso
8
Dieser Artikel erklärt sehr gut, warum es besser ist, paketweise statt paketweise zu verwenden .
Tom
@eljenso "Sie gehen nicht auf die Gründe ein, warum es nicht so ist" und versuchen, die Beweislast zu verlagern? Ich denke, der Artikel von Tom erklärt sehr gut, warum Package-by-Feature besser ist, und es fällt mir schwer, "Ich glaube nicht, dass es ein Urteilsspruch ist. Erst nach Schichten zu teilen, dann nach Funktionalität ist definitiv der richtige Weg ernsthaft gehen.
pka
18

Ich halte mich an die Verpackungsprinzipien von Onkel Bob . Kurz gesagt, Klassen, die zusammen wiederverwendet und gemeinsam geändert werden sollen (aus demselben Grund, z. B. eine Abhängigkeitsänderung oder eine Framework-Änderung), sollten in dasselbe Paket aufgenommen werden. IMO, die Funktionsaufteilung hätte bessere Chancen, diese Ziele zu erreichen als die vertikale / geschäftsspezifische Aufteilung in den meisten Anwendungen.

Beispielsweise kann ein horizontaler Teil von Domänenobjekten von verschiedenen Arten von Frontends oder sogar Anwendungen wiederverwendet werden, und ein horizontaler Teil des Web-Frontends ändert sich wahrscheinlich zusammen, wenn das zugrunde liegende Webframework geändert werden muss. Andererseits ist es leicht vorstellbar, wie sich diese Änderungen auf viele Pakete auswirken, wenn Klassen in verschiedenen Funktionsbereichen in diesen Paketen gruppiert sind.

Offensichtlich sind nicht alle Arten von Software gleich, und die vertikale Aufteilung kann in bestimmten Projekten sinnvoll sein (im Hinblick auf das Erreichen der Ziele der Wiederverwendbarkeit und der Nähe zu Änderungen).

Buu Nguyen
quelle
Vielen Dank, das ist sehr klar. Wie ich in der Frage sagte, habe ich das Gefühl, dass die Häufigkeit, mit der Sie Technologien austauschen, weitaus geringer ist, als wenn Sie sich um vertikale Funktionsbereiche bewegen würden. War das nicht deine Erfahrung?
Tim Visher
Es geht nicht nur um Technologien. Punkt 1 Ihres ursprünglichen Beitrags ist nur dann sinnvoll, wenn die vertikalen Segmente unabhängige Anwendungen / Dienste sind, die über eine Schnittstelle (SOA, wenn Sie so wollen) miteinander kommunizieren. mehr unten ...
Buu Nguyen
Wenn Sie sich nun mit den Details jeder dieser fein abgestimmten Apps / Dienste befassen, die über eine eigene Benutzeroberfläche / ein eigenes Geschäft / eigene Daten verfügen, kann ich mir kaum vorstellen, dass sich Änderungen in einem vertikalen Segment ergeben, sei es in Bezug auf Technologie, Abhängigkeit, Regel / Workflow oder Protokollierung , Sicherheit, UI-Stil, kann vollständig von anderen Slices isoliert werden.
Buu Nguyen
Ich würde mich über Ihr Feedback zu meiner Antwort
freuen
@BuuNguyen Der Link zu den Prinzipien des Verpackungsdesigns ist unterbrochen.
johnnieb
5

In der Regel sind beide Teilungsebenen vorhanden. Von oben gibt es Bereitstellungseinheiten. Diese werden als "logisch" bezeichnet (denken Sie in Ihren Begriffen an Eclipse-Funktionen). Innerhalb der Bereitstellungseinheit haben Sie eine funktionale Aufteilung der Pakete (denken Sie an Eclipse-Plugins).

Zum Beispiel Merkmal ist com.feature, und es besteht aus com.feature.client, com.feature.coreund com.feature.uiPlugins. Innerhalb von Plugins habe ich sehr wenig Unterteilung in andere Pakete, obwohl das auch nicht ungewöhnlich ist.

Update: Übrigens gibt es bei InfoQ ein großartiges Gespräch von Jürgen Hoeller über die Code-Organisation: http://www.infoq.com/presentations/code-organization-large-projects . Jürgen ist einer der Architekten des Frühlings und weiß viel über dieses Zeug.

Peter Štibraný
quelle
Ich folge nicht ganz. Normalerweise wird com.apache.wicket.x angezeigt, wobei x entweder funktional oder logisch ist. Normalerweise sehe ich com.x nicht. Ich denke, Sie sagen, dass Sie sich für eine com.company.project.feature.layer-Struktur entscheiden würden? Hast du gründe
Tim Visher
Grund dafür ist, dass "com.company.project.feature" eine Bereitstellungseinheit ist. Auf dieser Ebene sind einige Funktionen optional und können übersprungen (dh nicht bereitgestellt) werden. Innerhalb der Funktion sind die Dinge jedoch nicht optional, und Sie möchten normalerweise alle. Hier ist es sinnvoller, nach Schichten zu teilen.
Peter Štibraný
4

Die meisten Java-Projekte, an denen ich gearbeitet habe, schneiden die Java-Pakete zuerst funktional und dann logisch auf.

Normalerweise sind Teile so groß, dass sie in separate Build-Artefakte aufgeteilt werden, in denen Sie Kernfunktionen in ein Glas, Apis in ein anderes, Web-Frontend-Inhalte in ein Warfile usw. integrieren können.

Ben Hardy
quelle
Wie könnte das aussehen? domain.company.project.function.logicalSlice?
Tim Visher
ja schon! egdcpweb.profiles, dcpweb.registration, dcpapis, dcppersistence usw. funktionieren im Allgemeinen ziemlich gut, Ihr Kilometerstand kann variieren. Dies hängt auch davon ab, ob Sie die Domänenmodellierung verwenden. Wenn Sie mehrere Domänen haben, möchten Sie möglicherweise zuerst nach Domänen aufteilen.
Ben Hardy
Ich persönlich bevorzuge das Gegenteil, logisch und dann funktional. apples.rpc, apples.model und dann banana.model, banana.store
mP.
Es ist wertvoller, alle Apple-Inhalte zusammenzufassen, als alle Web-Inhalte zusammenzufassen.
mP.
3

Pakete sind als Einheit zusammenzustellen und zu verteilen. Bei der Überlegung, welche Klassen zu einem Paket gehören, sind die Abhängigkeiten eines der Hauptkriterien. Von welchen anderen Paketen (einschließlich Bibliotheken von Drittanbietern) diese Klasse abhängt. Ein gut organisiertes System gruppiert Klassen mit ähnlichen Abhängigkeiten in einem Paket. Dies begrenzt die Auswirkungen einer Änderung in einer Bibliothek, da nur wenige genau definierte Pakete davon abhängen.

Es hört sich so an, als ob Ihr logisches vertikales System dazu neigt, Abhängigkeiten über die meisten Pakete hinweg zu "verschmieren". Das heißt, wenn jedes Feature als vertikales Slice gepackt ist, hängt jedes Paket von jeder Bibliothek eines Drittanbieters ab, die Sie verwenden. Jede Änderung an einer Bibliothek wirkt sich wahrscheinlich auf Ihr gesamtes System aus.

erickson
quelle
Ah. Eine Sache, die horizontale Slices machen, ist, Sie vor tatsächlichen Upgrades in den Bibliotheken zu schützen, die Sie verwenden. Ich denke, Sie haben Recht, dass die vertikalen Schichten jede Abhängigkeit Ihres Systems von jedem Paket verschmieren. Warum ist das so eine große Sache?
Tim Visher
Für ein "Feature" ist es keine so große Sache. Sie sind tendenziell volatiler und haben sowieso mehr Abhängigkeiten. Andererseits weist dieser "Client" -Code tendenziell eine geringe Wiederverwendbarkeit auf. Für etwas, das Sie als wiederverwendbare Bibliothek beabsichtigen, ist es eine großartige Investition, Kunden von jeder kleinen Änderung zu isolieren.
Erickson
3

Ich persönlich bevorzuge es, Klassen logisch zu gruppieren und dann ein Unterpaket für jede funktionale Teilnahme einzuschließen.

Ziele der Verpackung

Bei Paketen geht es schließlich darum, Dinge zu gruppieren - die Idee, verwandte Klassen zu sein, lebt nahe beieinander. Wenn sie im selben Paket leben, können sie das Paket private nutzen, um die Sichtbarkeit einzuschränken. Das Problem besteht darin, dass Sie alle Ihre Ansichten und Beständigkeitsdaten in einem Paket zusammenfassen. Dies kann dazu führen, dass viele Klassen in einem einzigen Paket zusammengefasst werden. Als nächstes ist es sinnvoll, Ansicht, Persistenz, Unterpakete und Refactor-Klassen entsprechend zu erstellen. Leider unterstützt das geschützte und private Paketumfang das Konzept des aktuellen Pakets und Unterpakets nicht, da dies bei der Durchsetzung solcher Sichtbarkeitsregeln hilfreich wäre.

Ich sehe jetzt Wert in der Trennung über Funktionalität, weil welcher Wert da ist, um alle sichtbezogenen Dinge zu gruppieren. Die Dinge in dieser Benennungsstrategie werden mit einigen Klassen in der Ansicht getrennt, während andere hartnäckig sind und so weiter.

Ein Beispiel für meine logische Verpackungsstruktur

Zur Veranschaulichung können wir zwei Module benennen. Verwenden Sie das Namensmodul als Konzept, das Klassen unter einem bestimmten Zweig eines Paketbaums gruppiert.

apple.model apple.store banana.model banana.store

Vorteile

Ein Client, der den Banana.store.BananaStore verwendet, ist nur der Funktionalität ausgesetzt, die wir zur Verfügung stellen möchten. Die Version im Ruhezustand ist ein Implementierungsdetail, das sie nicht kennen müssen und das sie auch nicht sehen sollten, wenn sie den Speichervorgängen Unordnung hinzufügen.

Weitere logische v Funktionsvorteile

Je weiter oben an der Wurzel, desto breiter wird der Umfang und die Dinge, die zu einem Paket gehören, zeigen immer mehr Abhängigkeiten von Dingen, die zu anderen Modulen gehören. Wenn man zum Beispiel das "Bananen" -Modul untersuchen würde, wären die meisten Abhängigkeiten auf dieses Modul beschränkt. Tatsächlich würden die meisten Helfer unter "Banane" außerhalb dieses Paketumfangs überhaupt nicht referenziert.

Warum Funktionalität?

Welchen Wert erreicht man, wenn man Dinge auf der Grundlage der Funktionalität zusammenfasst? In einem solchen Fall sind die meisten Klassen unabhängig voneinander, wobei die privaten Methoden oder Klassen des Pakets kaum oder gar nicht genutzt werden müssen. Das Umgestalten in ihre eigenen Unterpakete bringt wenig, trägt aber dazu bei, die Unordnung zu verringern.

Entwickler ändert das System

Wenn Entwickler beauftragt werden, Änderungen vorzunehmen, die etwas mehr als trivial sind, erscheint es albern, dass sie möglicherweise Änderungen haben, die Dateien aus allen Bereichen des Paketbaums enthalten. Mit dem logisch strukturierten Ansatz sind ihre Änderungen lokaler im selben Teil des Paketbaums, der gerade richtig erscheint.

mP.
quelle
"Wenn Entwickler [...] Dateien aus allen Bereichen des Paketbaums." Sie haben Recht, wenn Sie sagen, dass dies albern erscheint. Denn genau hier geht es um die richtige Überlagerung: Änderungen wirken sich nicht auf die gesamte Struktur der Anwendung aus.
Eljenso
Danke für den Hinweis. Ich bin mir nicht sicher, ob ich dich klar verstehe. Ich glaube, Sie versuchen, für logische Pakete zu argumentieren. Ich weiß nicht genau, warum. Könnten Sie versuchen, Ihre Antwort ein wenig klarer zu machen, möglicherweise umformulieren? Vielen Dank!
Tim Visher
3

Beide funktionale (architektonische) als auch logische (Merkmals-) Verpackungsansätze haben ihren Platz. Viele Beispielanwendungen (die in Lehrbüchern usw. enthalten sind) folgen dem funktionalen Ansatz, Präsentation, Geschäftsdienste, Datenzuordnung und andere Architekturebenen in separate Pakete zu packen. In Beispielanwendungen hat jedes Paket oft nur wenige oder nur eine Klasse.

Dieser anfängliche Ansatz ist in Ordnung, da ein erfundenes Beispiel häufig dazu dient: 1) die Architektur des präsentierten Frameworks konzeptionell abzubilden, 2) dies mit einem einzigen logischen Zweck zu tun (z. B. Hinzufügen / Entfernen / Aktualisieren / Löschen von Haustieren aus einer Klinik). . Das Problem ist, dass viele Leser dies als einen Standard betrachten, der keine Grenzen kennt.

Wenn eine "Geschäfts" -Anwendung erweitert wird, werden immer mehr Funktionen hinzugefügt funktionalen Ansatzes zu einer Belastung. Obwohl ich weiß, wo ich nach Typen suchen muss, die auf der Architekturschicht basieren (z. B. Webcontroller unter einem "Web" - oder "UI" -Paket usw.), muss für die Entwicklung einer einzelnen logischen Funktion zwischen vielen Paketen hin und her gesprungen werden. Das ist zumindest umständlich, aber es ist schlimmer als das.

Da logisch verwandte Typen nicht zusammen gepackt werden, wird die API übermäßig veröffentlicht. Die Interaktion zwischen logisch verwandten Typen muss "öffentlich" sein, damit Typen importiert und miteinander interagieren können (die Möglichkeit, die Sichtbarkeit auf Standardwerte / Pakete zu minimieren, geht verloren).

Wenn ich eine Framework-Bibliothek erstelle, folgen meine Pakete auf jeden Fall einem funktionalen / architektonischen Verpackungsansatz. Meine API-Konsumenten wissen möglicherweise sogar zu schätzen, dass ihre Importanweisungen ein intuitives Paket enthalten, das nach der Architektur benannt ist.

Umgekehrt werde ich beim Erstellen einer Geschäftsanwendung nach Funktionen packen. Ich habe kein Problem damit, Widget, WidgetService und WidgetController alle im selben " com.myorg.widget " -Paket zu platzieren und dann die Standardsichtbarkeit zu nutzen (und weniger Importanweisungen sowie Abhängigkeiten zwischen Paketen zu haben).

Es gibt jedoch Überkreuzungsfälle. Wenn mein WidgetService von vielen logischen Domänen (Funktionen) verwendet wird, kann ich ein Paket " com.myorg.common.service. " Erstellen . Es besteht auch eine gute Chance, dass ich Klassen mit der Absicht erstelle, funktionsübergreifend wiederverwendbar zu sein und Pakete wie " com.myorg.common.ui.helpers. " Und " com.myorg.common.util " zu erhalten. Möglicherweise verschiebe ich sogar alle diese späteren "allgemeinen" Klassen in ein separates Projekt und füge sie als myorg-commons.jar-Abhängigkeit in meine Geschäftsanwendung ein.

i3ensays
quelle
2

Kommt es auf die Granularität Ihrer logischen Prozesse an?

Wenn sie eigenständig sind, haben Sie häufig ein neues Projekt für sie in der Quellcodeverwaltung und kein neues Paket.

Das Projekt, an dem ich gerade arbeite, geht in Richtung logische Aufteilung. Es gibt ein Paket für den Jython-Aspekt, ein Paket für eine Regel-Engine, Pakete für foo, bar, binglewozzle usw. Ich möchte die XML-spezifischen Parser haben /schreiber für jedes Modul in diesem Paket, anstatt ein XML-Paket zu haben (was ich zuvor getan habe), obwohl es immer noch ein XML-Kernpaket geben wird, in dem gemeinsame Logik verwendet wird. Ein Grund dafür ist jedoch, dass es möglicherweise erweiterbar ist (Plugins) und daher jedes Plugin auch seinen XML-Code (oder Datenbankcode usw.) definieren muss, sodass eine Zentralisierung später zu Problemen führen kann.

Am Ende scheint es so zu sein, wie es für das jeweilige Projekt am sinnvollsten erscheint. Ich denke, es ist jedoch einfach, nach dem Vorbild des typischen Projektschichtdiagramms zu verpacken. Sie erhalten eine Mischung aus logischer und funktionaler Verpackung.

Was benötigt wird, sind markierte Namespaces. Ein XML-Parser für einige Jython-Funktionen kann sowohl mit Jython als auch mit XML gekennzeichnet werden, anstatt den einen oder anderen auswählen zu müssen.

Oder vielleicht wackle ich.

JeeBee
quelle
Ich verstehe Ihren Standpunkt nicht ganz. Ich denke, was Sie sagen, ist, dass Sie nur das tun sollten, was für Ihr Projekt am sinnvollsten ist, und für Sie ist das logisch, mit ein wenig Funktionalität. Gibt es bestimmte praktische Gründe dafür?
Tim Visher
Wie gesagt, ich werde Plugins haben, um die eingebaute Funktionalität zu erweitern. Ein Plugin muss in der Lage sein, sein XML (und schließlich in die Datenbank) zu analysieren und zu schreiben sowie Funktionen bereitzustellen. Habe ich daher com.xx.feature.xml oder com.xx.xml.feature? Ersteres scheint ordentlicher.
JeeBee
2

Ich versuche, Paketstrukturen so zu gestalten, dass es beim Zeichnen eines Abhängigkeitsgraphen einfach ist, einem konsistenten Muster mit möglichst wenigen Zirkelverweisen zu folgen und es zu verwenden.

Für mich ist dies in einem vertikalen Benennungssystem viel einfacher zu pflegen und zu visualisieren als in einem horizontalen. Wenn component1.display einen Verweis auf component2.dataaccess hat, werden mehr Warnglocken ausgelöst, als wenn display.component1 einen Verweis auf dataaccess hat. Komponente2.

Natürlich werden die von beiden gemeinsam genutzten Komponenten in einem eigenen Paket zusammengefasst.

Levand
quelle
So wie ich Sie damals verstehe, würden Sie sich für die vertikale Namenskonvention einsetzen. Ich denke, dafür ist ein * .utils-Paket gedacht, wenn Sie eine Klasse über mehrere Slices hinweg benötigen.
Tim Visher
2

Ich folge voll und ganz und schlage die logische ("by-feature") Organisation vor! Ein Paket sollte dem Konzept eines "Moduls" so genau wie möglich folgen. Die funktionale Organisation kann ein Modul über ein Projekt verteilen, was zu einer geringeren Kapselung führt und anfällig für Änderungen der Implementierungsdetails ist.

Nehmen wir zum Beispiel ein Eclipse-Plugin: Alle Ansichten oder Aktionen in einem Paket zusammenzufassen, wäre ein Chaos. Stattdessen sollte jede Komponente eines Features in das Paket des Features oder, falls es viele gibt, in Unterpakete (featureA.handlers, featureA.preferences usw.) verschoben werden.

Das Problem liegt natürlich im hierarchischen Paketsystem (das unter anderem Java hat), das den Umgang mit orthogonalen Belangen unmöglich oder zumindest sehr schwierig macht - obwohl sie überall auftreten!

thSoft
quelle
1

Ich würde mich persönlich für eine funktionale Benennung entscheiden. Der kurze Grund: Es vermeidet Codeduplizierung oder Abhängigkeitsalptraum.

Lassen Sie mich etwas näher darauf eingehen. Was passiert, wenn Sie eine externe JAR-Datei mit einem eigenen Paketbaum verwenden? Sie importieren effektiv den (kompilierten) Code in Ihr Projekt und damit einen (funktional getrennten) Paketbaum. Wäre es sinnvoll, die beiden Namenskonventionen gleichzeitig zu verwenden? Nein, es sei denn, das war dir verborgen. Und es ist, wenn Ihr Projekt klein genug ist und eine einzelne Komponente hat. Wenn Sie jedoch mehrere logische Einheiten haben, möchten Sie das Modul zum Laden von Datendateien wahrscheinlich nicht erneut implementieren. Sie möchten es zwischen logischen Einheiten teilen, keine künstlichen Abhängigkeiten zwischen logisch nicht verwandten Einheiten haben und müssen nicht auswählen, in welche Einheit Sie dieses bestimmte gemeinsam genutzte Tool einfügen möchten.

Ich denke, aus diesem Grund wird die funktionale Benennung am häufigsten in Projekten verwendet, die eine bestimmte Größe erreichen oder erreichen sollen, und die logische Benennung wird in Klassennamenskonventionen verwendet, um die spezifische Rolle zu verfolgen, wenn eine der Klassen in einer Paket.

Ich werde versuchen, auf jeden Ihrer Punkte zur logischen Benennung genauer zu antworten.

  1. Wenn Sie in alten Klassen angeln müssen, um Funktionen zu ändern, wenn Sie Pläne ändern, ist dies ein Zeichen für eine schlechte Abstraktion: Sie sollten Klassen erstellen, die eine genau definierte Funktionalität bieten, die in einem kurzen Satz definiert werden kann. Nur wenige erstklassige Klassen sollten all diese zusammenstellen, um Ihre Business Intelligence widerzuspiegeln. Auf diese Weise können Sie mehr Code wiederverwenden, haben eine einfachere Wartung, eine klarere Dokumentation und weniger Abhängigkeitsprobleme.

  2. Das hängt hauptsächlich davon ab, wie Sie Ihr Projekt bearbeiten. Auf jeden Fall sind die logische und die funktionale Ansicht orthogonal. Wenn Sie also eine Namenskonvention verwenden, müssen Sie die andere auf Klassennamen anwenden, um eine bestimmte Reihenfolge beizubehalten, oder von einer Namenskonvention zu einer anderen in einer bestimmten Tiefe wechseln.

  3. Zugriffsmodifikatoren sind eine gute Möglichkeit, anderen Klassen, die Ihre Verarbeitung verstehen , den Zugriff auf die Innereien Ihrer Klasse zu ermöglichen. Logische Beziehung bedeutet nicht das Verständnis von algorithmischen oder Parallelitätsbeschränkungen. Funktionell kann, obwohl es nicht. Ich bin es sehr leid, andere Zugriffsmodifikatoren als öffentliche und private zu verwenden, da sie häufig einen Mangel an angemessener Architektur und Klassenabstraktion verbergen.

  4. In großen kommerziellen Projekten kommt es häufiger zu einem Technologiewechsel, als Sie glauben. Zum Beispiel musste ich den XML-Parser bereits dreimal, die Caching-Technologie zweimal und die Geolokalisierungssoftware zweimal ändern. Gut, dass ich alle Details in einem speziellen Paket versteckt hatte ...

Varkhan
quelle
1
Verzeihen Sie mir, wenn ich falsch liege, aber es klingt für mich so, als würden Sie eher über die Entwicklung eines Frameworks sprechen, das von vielen Menschen verwendet werden soll. Ich denke, die Regeln ändern sich zwischen dieser Art der Entwicklung und der Entwicklung von Endbenutzersystemen. Liege ich falsch?
Tim Visher
Ich denke, für allgemeine Klassen, die von vielen vertikalen Schichten verwendet werden, ist es sinnvoll, so etwas wie ein utilsPaket zu haben. Dann können alle Klassen, die es benötigen, einfach von diesem Paket abhängen.
Tim Visher
Auch hier kommt es auf die Größe an: Wenn ein Projekt groß genug wird, muss es von mehreren Personen unterstützt werden, und es kommt zu einer Spezialisierung. Um die Interaktion zu vereinfachen und Fehler zu vermeiden, ist eine schnelle Segmentierung des Projekts schnell erforderlich. Und das Utensilienpaket wird bald riesig!
Varkhan
1

Es ist ein interessantes Experiment, überhaupt keine Pakete zu verwenden (mit Ausnahme des Root-Pakets).

Die Frage, die sich dann stellt, ist, wann und warum es sinnvoll ist, Pakete einzuführen. Vermutlich unterscheidet sich die Antwort von der, die Sie zu Beginn des Projekts beantwortet hätten.

Ich gehe davon aus, dass Ihre Frage überhaupt auftaucht, weil Pakete wie Kategorien sind und es manchmal schwierig ist, sich für das eine oder andere zu entscheiden. Manchmal wäre es besser, wenn Tags mitteilen würden, dass eine Klasse in vielen Kontexten verwendet werden kann.

user53682
quelle
0

Aus rein praktischer Sicht ermöglichen die Sichtbarkeitskonstrukte von Java Klassen im selben Paket den Zugriff auf Methoden und Eigenschaften mit protectedund defaultSichtbarkeit sowie auf publicdiejenigen. Die Verwendung nicht öffentlicher Methoden aus einer völlig anderen Codeebene wäre definitiv ein großer Codegeruch. Daher neige ich dazu, Klassen aus derselben Ebene in dasselbe Paket zu packen.

Ich verwende diese geschützten oder Standardmethoden nicht oft an anderer Stelle - außer möglicherweise in den Komponententests für die Klasse -, aber wenn ich dies tue, stammt sie immer aus einer Klasse auf derselben Ebene

Bill Michell
quelle
Ist es nicht etwas von der Natur mehrschichtiger Systeme, immer von der nächsten Schicht abhängig zu sein? Zum Beispiel hängt die Benutzeroberfläche von Ihrer Serviceschicht ab, die von Ihrer Domänenschicht usw. abhängt. Das Zusammenpacken vertikaler Slices scheint vor übermäßigen Abhängigkeiten zwischen Paketen zu schützen, nicht wahr?
Tim Visher
Ich verwende fast nie geschützte und Standardmethoden. 99% sind entweder öffentlich oder privat. Einige Ausnahmen: (1) Standardsichtbarkeit für Methoden, die nur von Komponententests verwendet werden, (2) geschützte abstrakte Methode, die nur von der abstrakten Basisklasse verwendet wird.
Esko Luontola
1
Tim Visher, Abhängigkeiten zwischen Paketen sind kein Problem, solange die Abhängigkeiten immer in die gleiche Richtung zeigen und das Abhängigkeitsdiagramm keine Zyklen enthält.
Esko Luontola
0

Es hängt davon ab, ob. In meiner Arbeit teilen wir Pakete manchmal nach Funktionen (Datenzugriff, Analyse) oder nach Anlageklassen (Kredit, Aktien, Zinssätze) auf. Wählen Sie einfach die Struktur aus, die für Ihr Team am bequemsten ist.

quant_dev
quelle
Gibt es einen Grund, in beide Richtungen zu gehen?
Tim Visher
Die Aufteilung nach Assetklassen (allgemeiner: nach Geschäftsdomäne) erleichtert den neuen Personen den Weg durch den Code. Die Aufteilung nach Funktionen ist gut für die Kapselung. Für mich ist der "Paket" -Zugriff in Java mit einem "Freund" in C ++ vergleichbar.
quant_dev
@quant_dev Ich bin nicht einverstanden mit "..by Funktion ist gut für die Kapselung". Ich denke du meinst das genaue Gegenteil ist wahr. Wählen Sie auch nicht nur "Bequemlichkeit" aus. Für jeden gibt es einen Fall (siehe meine Antwort).
i3ensays
-3

Nach meiner Erfahrung verursacht die Wiederverwendbarkeit mehr Probleme als das Lösen. Mit den neuesten und billigsten Prozessoren und Speicher würde ich es vorziehen, Code zu duplizieren, anstatt ihn eng zu integrieren, um ihn wiederzuverwenden.

Raghu Kasturi
quelle
5
Bei der Wiederverwendung von Code geht es nicht um den Preis der Hardware, sondern um die Kosten für die Codewartung.
Benutzer2793390
1
Stimmen Sie zu, aber das Beheben von Problemen in einem einfachen Modul sollte nicht den gesamten Code betreffen. Über welche Wartungskosten sprechen Sie?
Raghu Kasturi
1
Eine Fehlerbehebung in einem Modul sollte sich auf die gesamte Anwendung auswirken. Warum sollten Sie denselben Fehler mehrmals beheben wollen?
user2793390