2009-03-22 13 views
26

Ich habe einige Metriken auf meinem Java-Projekt ausgeführt und anscheinend gibt es viele Abhängigkeitszyklen zwischen Paketen. Ich wusste nicht wirklich, wie ich Sachen zu Paketen organisieren sollte, also tat ich einfach, was für mich einen Sinn ergab, was offensichtlich falsch ist.Wie organisiert man Pakete (und verhindert Abhängigkeitszyklen)?

Mein Projekt ist ein neuronales Netzwerk-Framework. Neuronale Netze haben Neuronen, die über Verbindungen miteinander verbunden sind. Sie müssen aufeinander angewiesen sein. Es gibt jedoch auch verschiedene Arten von Neuronen, also dachte ich, es wäre eine gute Idee, sie alle in ihr eigenes "Neuronen" -Paket zu legen. Offensichtlich ist eine Verbindung kein Neuron, also sollte sie nicht im Paket enthalten sein, aber da sie sich aufeinander beziehen, habe ich jetzt eine zirkuläre Abhängigkeit.

Dies ist nur ein Beispiel, aber ich habe mehr Situationen wie diese. Wie gehst du mit solchen Situationen um?

Außerdem habe ich gelesen, dass Klassen in einem Paket höher in der Pakethierarchie nicht auf Klassen in Paketen, die tiefer sind, beziehen sollen. Dies würde bedeuten, dass eine NeuralNetwork-Klasse im Paket 'nn' nicht auf das Neuron im Paket 'nn.neurons' verweisen kann. Folgt ihr diesem Prinzip? Und was, wenn ich NeuralNetwork zu "nn.networks" oder so etwas bewegen würde? In diesem Fall würde es sich auf ein Geschwisterpaket anstelle eines Kindes beziehen. Ist das eine bessere Praxis?

Antwort

12

Die antcontrib VerifyDesign task helfen Ihnen, was Sie wollen:

Zum Beispiel, wenn es drei Pakete in einer Hand Baum

* biz.xsoftware.presentation 
* biz.xsoftware.business 
* biz.xsoftware.dataaccess 

und natürlich Präsentation sollte nur hängen von Business-Paket und Geschäft sollte auf Datenzugriff abhängen. Wenn Sie Ihr Design auf diese Weise definieren und es verletzt wird, wird der Build fehlschlagen, wenn die verifestdesign ant-Task aufgerufen wird. Wenn ich zum Beispiel eine Klasse in der biz.xsoftware.presentation erstellt habe und diese Klasse von einer Klasse in biz.xsoftware.dataaccess abhängig ist, würde der Build fehlschlagen. Dadurch wird sichergestellt, dass das Design tatsächlich dem entspricht, was dokumentiert ist (bis zu einem gewissen Grad mindestens ). Dies ist besonders schön mit automatisierten baut

Also, wenn Sie sich entschieden haben, wie die Dinge sollten Sie die Anforderungen bei der Kompilierung durchsetzen können organisiert werden. Sie erhalten auch fein granierte Kontrolle, so dass Sie bestimmten Fällen erlauben können, diese "Regeln" zu brechen. Sie können also einige Zyklen zulassen.

Je nachdem, wie Sie die Dinge tun möchten, könnten Sie feststellen, dass das Paket "utils" sinnvoll ist.

Für den Fall, dass Sie zitieren ... Ich könnte so etwas tun:

  • Paket nn enthält Nueron und Anschluss
  • Paket nn.neurons enthält die Subklassen von Nueron

Neuron und Connection sind beide High-Level-Konzepte, die im NeuralNetowrk verwendet werden, daher macht es Sinn, sie alle zusammenzusetzen. Die Klassen Neuron und Connection können sich aufeinander beziehen, während die Klasse Connection die Neuron-Unterklassen nicht kennen muss.

+1

Ich habe vor kurzem ein ähnliches Tool entwickelt, das annotierte Klassen anstelle einer zentralen Konfigurationsdatei verwendet: https: // github.com/ruediste1/jabsaw – ruediste

5

Ich glaube nicht, dass zyklische Abhängigkeiten wie die, die Sie beschreiben schlecht sein. Solange die Konzepte, die voneinander abhängig sind, sich auf derselben Abstraktionsebene befinden und sich auf dieselben Teile der Architektur beziehen, ist es möglicherweise nicht notwendig, diese voneinander zu verbergen. Neuronen und Verbindungen passen zu dieser Rechnung in meinem Verständnis.

Eine übliche Maßnahme zur Reduzierung solcher Kopplungen besteht darin, Schnittstellen zu extrahieren und möglicherweise sogar in ein separates Modul zu legen. Das einfache Organisieren von Paketen innerhalb eines einzelnen Projekts ermöglicht es Ihnen nicht, Implementierungsdetails ausreichend zu verbergen. Ein gemeinsames Muster, das Sie Implementierungen wirklich zu verbergen erlaubt ist wie folgt:

-Code-Client ----> Schnittstellen < --- Implementierung

In diesem Muster, können Sie die „Implementierung“ Modul aus dem Client-Code verstecken , was bedeutet, dass der Code im "Client code" -Modul nicht einmal den Implementierungscode sieht.

Die Verschachtelung von Paketen dient verschiedenen Zwecken: Einige Projekte verfügen möglicherweise über ein Domänenmodell, das in Paketen organisiert ist. In diesem Fall spiegeln die Pakete eine Gruppierung der Domäne wider, und Referenzen können nach oben/unten gehen. Wenn es um Dinge wie die Implementierung von Diensten geht, ist Ihr vorgeschlagenes Muster durchaus üblich und eine gute Sache, der Sie folgen sollten. Je tiefer in der Pakethierarchie, desto genauer wird die Klasse angenommen.

+0

ja, verifydesign Artikel EXPLICITY geht über diesen Anwendungsfall hier http://www.devx.com/opensource/Article/33729 –

4

Um welche Art von Code-Größe handelt es sich? Wenn Sie nur 10-20 Klassen haben, müssen Sie Ihren Code wahrscheinlich nicht einfach in Pakete überorganisieren (und sollten dies auch nicht tun).

Wenn Ihr Projekt wächst, besteht die erste Unterscheidung darin, den Benutzerschnittstellencode vom zugrunde liegenden Datamodell und der Logik zu trennen. Eine saubere Trennung der Schichten ist entscheidend, um eine ordnungsgemäße Einheitsprüfung durchführen zu können.

Wenn Sie Schwierigkeiten haben, die zirkulären Abhängigkeiten loszuwerden, ist es wahrscheinlich der Fall, dass die Klassen tatsächlich voneinander abhängig sind und sich im selben Paket befinden sollten.

Die richtigen Abstraktionsebenen zu erhalten, ist wahrscheinlich einer der wichtigsten Aspekte beim Entwurf der gesamten Code-Struktur.

5

Wie gehen Sie mit solchen Situationen um?

Zirkuläre Abhängigkeiten sind nicht von Natur aus schlecht. In der Tat kann dies manchmal der Fall sein, dass die Heilung schlimmer als die Krankheit ist: Das Extrahieren einer Schnittstelle erhöht die Komplexität Ihres Codes und fügt eine weitere Ebene der Indirektion hinzu. Das ist es wahrscheinlich nicht wert für sehr einfache Beziehungen.

9

Zuallererst sind Sie zu Recht besorgt, weil zirkuläre Abhängigkeiten zwischen Paketen schlecht sind. Probleme, die sich daraus ergeben, gewinnen mit der Größe des Projekts an Bedeutung, aber kein Grund, diese Situation pünktlich anzugehen.

Sie sollten Ihre Klassen organisieren, indem Sie Klassen, die Sie wiederverwenden, in demselben Paket platzieren. Also, wenn Sie zum Beispiel AbstractNeuron und AbstractConnection haben, würden Sie sie in das gleiche Paket legen. Wenn Sie jetzt die Implementierungen HumanNeuron und HumanConnection haben, würden Sie diese im selben Paket (z. B. * .network.human) platzieren. Oder Sie haben nur einen Verbindungstyp, zum Beispiel BaseConnection und viele verschiedene Neuronen. Das Prinzip bleibt gleich. Sie platzieren BaseConnection zusammen mit BaseNeuron. HumanNeuron in seinem eigenen Paket zusammen mit HumanSignal etc. VirtualNeuron zusammen mit VirtualSignal etc. Sie sagen: "Offensichtlich ist eine Verbindung kein Neuron, also sollte es nicht im Paket sein ..". Dies ist nicht so offensichtlich, noch richtig, um genau zu sein.

Sie sagen, Sie haben alle Neuronen im selben Paket platziert. Nichts davon ist korrekt, es sei denn, Sie verwenden alle Ihre Implementierungen zusammen. Sehen Sie sich nochmals das oben beschriebene Schema an. Entweder ist Ihr Projekt so klein, dass Sie alle in das einzelne Paket platzieren, oder Sie beginnen, Pakete wie beschrieben zu organisieren. Für weitere Details werfen Sie einen Blick auf The Common Reuse Principle:

DIE KLASSEN IN EINEM PAKET WERDEN ZUSAMMENGEWÄHLT. WENN SIE EINE DER KLASSEN IN EINEM PAKET WIEDER VERWENDEN, WIEDERVERWENDEN SIE SIE ALLE.

Verwandte Themen