Obwohl dies formal legitim ist, kann dies auf einen Codegeruch hinweisen . Um herauszufinden, ob dies in Ihrem Fall der Fall ist, stellen Sie sich drei Fragen:
- Wirst du es brauchen?
- Warum haben Sie es so verpackt?
- Wie können Sie feststellen, ob Ihre Paket-API bequem zu verwenden ist?
Wirst du es brauchen?
Wenn Sie keinen Grund sehen, zusätzlichen Aufwand in die Wartbarkeit von Code zu investieren , sollten Sie ihn unverändert lassen. Dieser Ansatz basiert auf dem YAGNI-Prinzip
Programmierer sollten keine Funktionen hinzufügen, bis dies als notwendig erachtet wird ... "Implementieren Sie Dinge immer dann, wenn Sie sie tatsächlich benötigen, niemals, wenn Sie nur vorhersehen, dass Sie sie benötigen."
Die folgenden Überlegungen gehen davon aus, dass der Aufwand für die weitere Analyse gerechtfertigt ist, wenn Sie davon ausgehen, dass der Code langfristig beibehalten wird, oder wenn Sie daran interessiert sind, Fähigkeiten zum Schreiben von wartbarem Code zu üben und zu verbessern. Wenn dies nicht zutrifft, können Sie den Rest überspringen - weil Sie ihn nicht brauchen werden .
Warum haben Sie es so verpackt?
Das erste, was erwähnenswert ist, ist, dass keine der offiziellen Kodierungskonventionen und Tutorials eine Anleitung dazu gibt, was ein starkes Signal dafür ist, dass diese Art von Entscheidungen im Ermessen eines Programmierers liegen wird.
Wenn Sie sich jedoch das von Entwicklern von JDK implementierte Design ansehen , werden Sie feststellen, dass das "Durchsickern" von Unterpaket-APIs in übergeordnete APIs dort nicht praktiziert wird. Schauen Sie sich zum Beispiel das gleichzeitige util-Paket und seine Unterpakete an - Locks und Atomic .
- Beachten Sie, dass die Verwendung der API für Unterpakete im Inneren als OK angesehen wird. Beispielsweise importiert die ConcurrentHashMap-Implementierung Sperren und verwendet ReentrantLock. Die Locks-API wird einfach nicht als öffentlich angezeigt.
Okay, Kern-API-Entwickler scheinen das zu vermeiden und herauszufinden, warum es so ist, fragen Sie sich, warum Sie es so verpackt haben? Der natürliche Grund dafür wäre der Wunsch , den Paketbenutzern eine Art Hierarchie, eine Art von Ebenen , mitzuteilen, so dass das Unterpaket den Konzepten niedrigerer Ebene / spezialisierter / engerer Verwendung entspricht.
Dies geschieht häufig, um die Verwendung und das Verständnis der API zu vereinfachen und API-Benutzern den Betrieb innerhalb einer bestimmten Ebene zu erleichtern, ohne sie mit Bedenken anderer Ebenen zu belästigen. Wenn dies der Fall ist, würde das Offenlegen einer API auf niedrigerer Ebene aus Unterpaketen Ihrer Absicht widersprechen.
- Randnotiz: Wenn Sie nicht sagen können, warum Sie es so verpacken, sollten Sie zu Ihrem Design zurückkehren und es gründlich analysieren, bis Sie dies verstanden haben. Denken Sie daran, wenn es Ihnen selbst jetzt noch schwer zu erklären ist, wie schwierig wäre es dann für diejenigen, die Ihr Paket in Zukunft warten und verwenden werden?
Wie können Sie feststellen, ob Ihre Paket-API bequem zu verwenden ist?
Aus diesem Grund empfehle ich dringend , Tests zu schreiben, die die von Ihrem Paket bereitgestellte API ausführen. Es würde auch nicht schaden, jemanden zur Überprüfung zu bitten, aber ein erfahrener API-Prüfer wird Sie höchstwahrscheinlich trotzdem bitten, solche Tests durchzuführen. Es ist schwer zu übertreffen, ein echtes Anwendungsbeispiel zu sehen, wenn man die API überprüft.
- Man kann sich die Klasse mit einer einzelnen Methode mit einem einzelnen Parameter und einem Traum ansehen, wow, wie einfach , aber wenn der Test zum Aufrufen die Erstellung von 10 anderen Objekten erfordert, die wie 20 Methoden mit insgesamt 50 Parametern aufrufen, bricht dies eine gefährliche Illusion und zeigt die wahre Komplexität von das Design.
Ein weiterer Vorteil von Tests besteht darin, dass diese die Bewertung verschiedener Ideen, die Sie zur Verbesserung Ihres Designs in Betracht ziehen, erheblich vereinfachen können.
Es kam mir manchmal vor, dass relativ geringfügige Implementierungsänderungen zu erheblichen Vereinfachungen bei Tests führten und die Anzahl der Importe, Objekte, Methodenaufrufe und Parameter reduzierten. Dies ist bereits vor dem Ausführen von Tests ein guter Hinweis darauf, wie es sich lohnt, weiter zu verfolgen.
Das Gegenteil ist mir auch passiert - das heißt, als große, ehrgeizige Änderungen in meinen Implementierungen, die die API vereinfachen sollen, durch entsprechende geringfügige, unbedeutende Auswirkungen auf Tests als falsch erwiesen wurden, was mir half, die fruchtlosen Ideen fallen zu lassen und den Code unverändert zu lassen (mit vielleicht nur einem Kommentar) hinzugefügt, um den Hintergrund des Designs zu verstehen und anderen zu helfen, den falschen Weg zu lernen).
Für Ihren konkreten Fall ist das erste, was Ihnen in den Sinn kommt, die Änderung der Vererbung in Komposition in Betracht zu ziehen - das heißt, anstatt SomeClass
direkt zu implementieren Something
, ein Objekt einzuschließen, das diese Schnittstelle innerhalb der Klasse implementiert.
package me.my;
import me.my.pkg.Something;
public class SomeClass {
private Something something = new Something() {
/* ... implementation of Something goes here ... */
}
/* ... some more method implementations go here too ... */
}
Dieser Ansatz kann sich in Zukunft auszahlen und das Risiko verhindern, dass eine Unterklasse SomeClass
versehentlich eine Methode überschreibt Something
, sodass sie sich so verhält, wie Sie es nicht geplant haben.