Ich habe eine Klasse, in der jede Methode auf die gleiche Weise beginnt:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
Gibt es eine gute Möglichkeit, den fooIsEnabled
Teil für jede öffentliche Methode in der Klasse zu fordern (und hoffentlich nicht jedes Mal zu schreiben) ?
java
design-patterns
kristina
quelle
quelle
Antworten:
Ich weiß ja nicht , elegant, aber hier ist eine funktionierende Implementierung mit Hilfe von Java-internen
java.lang.reflect.Proxy
dass erzwingt , dass alle Methodenaufrufe aufFoo
zunächst die Überprüfungenabled
Zustand.main
Methode:Foo
Schnittstelle:FooFactory
Klasse:Wie andere bereits betont haben, scheint es ein Overkill für das zu sein, was Sie brauchen, wenn Sie nur eine Handvoll Methoden haben, über die Sie sich Sorgen machen müssen.
Das heißt, es gibt sicherlich Vorteile:
Foo
die Methodenimplementierungen nicht um dieenabled
Bedenken hinsichtlich der Prüfung kümmern müssen . Stattdessen muss sich der Code der Methode nur um den Hauptzweck der Methode kümmern, nicht mehr.Foo
Klasse eine neue Methode hinzuzufügen und fälschlicherweise zu vergessen, denenabled
Scheck hinzuzufügen . Dasenabled
Überprüfungsverhalten wird automatisch von jeder neu hinzugefügten Methode übernommen.enabled
Prüfung verbessern müssen , ist dies sehr einfach, sicher und an einem Ort.Spring
, obwohl dies definitiv auch eine gute Option sein kann.Um fair zu sein, sind einige der Nachteile:
FooImpl
es hässlich ist , innere Klassen zu haben, um die Instanziierung der Klasse zu verhindern .Foo
, müssen Sie eine Änderung an zwei Stellen vornehmen: der Implementierungsklasse und der Schnittstelle. Keine große Sache, aber es ist noch ein bisschen mehr Arbeit.BEARBEITEN:
Fabian Streitels Kommentar brachte mich dazu, über zwei Ärger mit meiner obigen Lösung nachzudenken, die ich zugeben muss, dass ich nicht glücklich über mich selbst bin:
Um Punkt 1 zu lösen und das Problem mit Punkt 2 zumindest zu lösen, würde ich eine Anmerkung
BypassCheck
(oder etwas Ähnliches) erstellen , mit der ich die Methoden in derFoo
Schnittstelle markieren könnte, für die ich das " aktivierte Prüfung ". Auf diese Weise benötige ich überhaupt keine magischen Zeichenfolgen, und es wird für Entwickler viel einfacher, in diesem speziellen Fall eine neue Methode korrekt hinzuzufügen.Bei Verwendung der Anmerkungslösung würde der Code folgendermaßen aussehen:
main
Methode:BypassCheck
Anmerkung:Foo
Schnittstelle:FooFactory
Klasse:quelle
Es gibt viele gute Vorschläge. Was Sie tun können, um Ihr Problem zu lösen, ist, im Staatsmuster zu denken und es umzusetzen.
Schauen Sie sich dieses Code-Snippet an. Vielleicht bringt es Sie auf eine Idee. In diesem Szenario möchten Sie die gesamte Methodenimplementierung basierend auf dem internen Status des Objekts ändern. Bitte denken Sie daran, dass die Summe der Methoden in einem Objekt als Verhalten bezeichnet wird.
Hoffe du magst es!
PD: Ist eine Implementierung des Staatsmusters (wird je nach Kontext auch als Strategie bezeichnet. Die Prinzipien sind jedoch dieselben).
quelle
bar()
inFooEnabledBehavior
undbar()
inFooDisabledBehavior
möglicherweise viel von demselben Code verwendet wird, wobei sich möglicherweise sogar eine einzelne Zeile zwischen den beiden unterscheidet. Sie könnten sehr leicht, besonders wenn dieser Code von Junior-Entwicklern (wie mir) gepflegt würde, zu einem riesigen Durcheinander von Mist führen, der nicht zu warten und nicht zu testen ist. Das kann mit jedem Code passieren, aber es scheint so einfach zu sein, es sehr schnell zu vermasseln. +1 aber, weil guter Vorschlag.Ja, aber es ist ein bisschen Arbeit, also hängt es davon ab, wie wichtig es für Sie ist.
Sie können die Klasse als Schnittstelle definieren, eine Delegatenimplementierung schreiben und dann
java.lang.reflect.Proxy
die Schnittstelle mit Methoden implementieren, die den gemeinsam genutzten Teil ausführen, und dann den Delegaten bedingt aufrufen.Sie
MyInvocationHandler
können ungefähr so aussehen (Fehlerbehandlung und Klassengerüst entfallen, vorausgesetzt, siefooIsEnabled
sind an einem zugänglichen Ort definiert):Es ist nicht unglaublich hübsch. Aber im Gegensatz zu verschiedenen Kommentatoren würde ich es tun, da ich denke, dass Wiederholung ein wichtigeres Risiko ist als diese Art von Dichte, und Sie werden in der Lage sein, das "Gefühl" Ihrer realen Klasse zu erzeugen, wenn dieser etwas unergründliche Wrapper hinzugefügt wird sehr lokal in nur wenigen Codezeilen.
Weitere Informationen zu dynamischen Proxy-Klassen finden Sie in der Java-Dokumentation .
quelle
Diese Frage steht in engem Zusammenhang mit der aspektorientierten Programmierung . AspectJ ist eine AOP-Erweiterung von Java, und Sie können einen Blick darauf werfen, um etwas Inspiration zu erhalten.
Soweit ich weiß, gibt es in Java keine direkte Unterstützung für AOP. Es gibt einige GOF-Muster, die sich darauf beziehen, wie zum Beispiel Vorlagenmethode und Strategie, aber es werden Sie nicht wirklich Codezeilen speichern.
In Java und den meisten anderen Sprachen können Sie die wiederkehrende Logik definieren, die Sie in Funktionen benötigen, und einen sogenannten disziplinierten Codierungsansatz anwenden, bei dem Sie sie zum richtigen Zeitpunkt aufrufen.
Dies würde jedoch nicht zu Ihrem Fall passen, da Sie möchten, dass der ausgerechnete Code zurückgegeben werden kann
checkBalance
. In Sprachen, die Makros unterstützen (wie C / C ++), können Sie diese als Makros definierencheckSomePrecondition
undcheckSomePostcondition
sie werden einfach durch den Präprozessor ersetzt, bevor der Compiler überhaupt aufgerufen wird:Java hat dies nicht sofort einsatzbereit. Dies mag jemanden beleidigen, aber ich habe in der Vergangenheit automatische Codegenerierungs- und Vorlagen-Engines verwendet, um sich wiederholende Codierungsaufgaben zu automatisieren. Wenn Sie Ihre Java-Dateien verarbeiten, bevor Sie sie mit einem geeigneten Präprozessor kompilieren, z. B. Jinja2, können Sie etwas Ähnliches tun, wie es in C möglich ist.
Möglicher reiner Java-Ansatz
Wenn Sie nach einer reinen Java-Lösung suchen, wird das, was Sie möglicherweise finden, wahrscheinlich nicht präzise sein. Es könnte jedoch weiterhin allgemeine Teile Ihres Programms herausrechnen und Codeduplikationen und Fehler vermeiden. Sie könnten so etwas tun (es ist eine Art Strategie- inspiriertes Muster). Beachten Sie, dass dieser Ansatz in C # und Java 8 sowie in anderen Sprachen, in denen Funktionen etwas einfacher zu handhaben sind, möglicherweise tatsächlich gut aussieht.
Ein bisschen wie ein reales Szenario
Sie entwickeln eine Klasse zum Senden von Datenrahmen an einen Industrieroboter. Der Roboter braucht Zeit, um einen Befehl auszuführen. Sobald der Befehl abgeschlossen ist, sendet er Ihnen einen Kontrollrahmen zurück. Der Roboter kann beschädigt werden, wenn er einen neuen Befehl erhält, während der vorherige noch ausgeführt wird. Ihr Programm verwendet eine
DataLink
Klasse zum Senden und Empfangen von Frames zum und vom Roboter. Sie müssen den Zugriff auf dieDataLink
Instanz schützen .Die Benutzeroberfläche Thread Anrufe
RobotController.left
,right
,up
oder ,down
wenn der Benutzer klickt auf die Tasten, sondern ruft auchBaseController.tick
in regelmäßigen Abständen, um zu reaktivieren Befehlsweiterleitung an die privatenDataLink
Instanz.quelle
protect
das einfacher wiederzuverwenden und zu dokumentieren ist. Wenn Sie dem zukünftigen Betreuer mitteilen, dass kritischer Code geschützt werden sollprotect
, sagen Sie ihm bereits, was zu tun ist. Wenn sich die Schutzregeln ändern, ist der neue Code weiterhin geschützt. Dies ist genau die Begründung für die Definition von Funktionen, aber das OP musste ein "zurückgebenreturn
", was Funktionen nicht können.Ich würde ein Refactoring in Betracht ziehen. Dieses Muster bricht das DRY-Muster stark (Wiederholen Sie sich nicht). Ich glaube, das bricht diese Klassenverantwortung. Dies hängt jedoch von Ihrer Code-Kontrolle ab. Ihre Frage ist sehr offen - wo rufen Sie die
Foo
Instanz an?Ich nehme an, Sie haben Code wie
Vielleicht solltest du es so nennen:
Und halte es sauber. Zum Beispiel Protokollierung:
a
logger
fragt sich nicht , ob das Debuggen aktiviert ist. Es protokolliert nur.Stattdessen muss die aufrufende Klasse Folgendes überprüfen:
Wenn dies eine Bibliothek ist und Sie den Aufruf dieser Klasse nicht steuern können, werfen Sie eine,
IllegalStateException
die erklärt, warum, wenn dieser Aufruf illegal ist und Probleme verursacht.quelle
IMHO ist die eleganteste und leistungsstärkste Lösung dafür, mehr als eine Implementierung von Foo zusammen mit einer Factory-Methode zum Erstellen einer zu haben:
Oder eine Variation:
Wie sstan betont, wäre es noch besser, eine Schnittstelle zu verwenden:
Passen Sie dies an den Rest Ihrer Umstände an (erstellen Sie jedes Mal ein neues Foo oder verwenden Sie dieselbe Instanz wieder usw.)
quelle
Foo
der erweiterten Klasse hinzuzufügen und zu vergessen, die No-Op-Version der Methode hinzuzufügen, wodurch das gewünschte Verhalten umgangen wird.isFooEnabled
bei der Instanziierung vonFoo
. Es ist zu früh. Im ursprünglichen Code erfolgt dies, wenn eine Methode ausgeführt wird. Der Wert vonisFooEnabled
kann sich in der Zwischenzeit ändern.fooIsEnabled
kann konstant sein. Aber nichts sagt uns, dass es konstant ist. Ich betrachte also den allgemeinen Fall.Ich habe einen anderen Ansatz: habe eine
}}
und dann kannst du tun
Vielleicht können Sie das sogar durch
isFooEnabled
eineFoo
Variable ersetzen , die entweder dasFooImpl
zu verwendende oder das enthältNullFoo.DEFAULT
. Dann ist der Anruf wieder einfacher:Übrigens wird dies als "Nullmuster" bezeichnet.
quelle
(isFooEnabled ? foo : NullFoo.DEFAULT).bar();
scheint etwas ungeschickt. Haben Sie eine dritte Implementierung, die an eine der vorhandenen Implementierungen delegiert. Anstatt den Wert eines FeldesisFooEnabled
zu ändern, könnte das Delegierungsziel geändert werden. Dies reduziert die Anzahl der Zweige im CodeFoo
im Aufrufcode! Wie können wir wissen, obisFooEnabled
? Dies ist ein internes Feld in der KlasseFoo
.In einem ähnlichen funktionalen Ansatz wie bei @ Colins Antwort ist es mit den Lambda-Funktionen von Java 8 möglich, den Code zum Aktivieren / Deaktivieren der bedingten Funktion in eine Guard-Methode (
executeIfEnabled
) zu packen, die die Aktion Lambda akzeptiert, für die der bedingt auszuführende Code verwendet werden kann bestanden.Obwohl in diesem Fall bei diesem Ansatz keine Codezeilen gespeichert werden, haben Sie jetzt die Möglichkeit, andere Probleme beim Umschalten von Funktionen sowie AOP- oder Debugging-Probleme wie Protokollierung, Diagnose, Profilerstellung usw. zu zentralisieren.
Ein Vorteil der Verwendung von Lambdas besteht darin, dass Verschlüsse verwendet werden können, um zu vermeiden, dass die
executeIfEnabled
Methode überlastet werden muss.Beispielsweise:
Mit einem Test:
quelle
Wie in anderen Antworten ausgeführt, ist das Strategie-Entwurfsmuster ein geeignetes Entwurfsmuster, um diesen Code zu vereinfachen. Ich habe es hier mithilfe des Methodenaufrufs durch Reflexion veranschaulicht, aber es gibt eine beliebige Anzahl von Mechanismen, mit denen Sie den gleichen Effekt erzielen können.
quelle
Proxy
.foo.fooIsEnabled ...
? A priori ist dies ein internes Feld des Objekts, wir können und wollen es nicht draußen sehen.Wenn nur Java ein bisschen besser darin wäre, funktionsfähig zu sein. Es wird davon ausgegangen, dass die OOO-Lösung darin besteht, eine Klasse zu erstellen, die eine einzelne Funktion umschließt, sodass sie nur aufgerufen wird, wenn foo aktiviert ist.
und dann implementieren
bar
,baz
undbat
als anonyme Klassen, dass erweiternFunctionWrapper
.Eine andere Idee
Verwenden Sie die nullbare Lösung von glglgl, aber machen Sie
FooImpl
undNullFoo
innere Klassen (mit privaten Konstruktoren) der folgenden Klasse:Auf diese Weise müssen Sie sich keine Gedanken mehr über die Verwendung machen
(isFooEnabled ? foo : NullFoo.DEFAULT)
.quelle
Foo foo = new Foo()
zu rufenbar
, würden Sie schreibenfoo.bar.call()
Es scheint, als würde die Klasse nichts tun, wenn Foo nicht aktiviert ist. Warum also nicht auf einer höheren Ebene ausdrücken, auf der Sie die Foo-Instanz erstellen oder abrufen?
Dies funktioniert jedoch nur, wenn isFooEnabled eine Konstante ist. Im Allgemeinen können Sie Ihre eigene Anmerkung erstellen.
quelle
fooIsEnabled
beim Aufrufen einer Methode. Sie tun dies vor der Instanziierung vonFoo
. Es ist zu früh. Der Wert kann sich in der Zwischenzeit ändern.isFooEnabled
ist ein Instanzfeld vonFoo
Objekten.Ich bin mit der Java-Syntax nicht vertraut. Annahme, dass es in Java Polymorphismus, statische Eigenschaften, abstrakte Klasse und Methode gibt:
quelle
new bar()
soll das heißen?Name
mit einem Großbuchstaben.bar()
. Wenn Sie das ändern müssen, sind Sie zum Scheitern verurteilt.Grundsätzlich haben Sie ein Flag, dass der Funktionsaufruf übersprungen werden sollte, wenn er gesetzt ist. Ich denke, meine Lösung wäre dumm, aber hier ist sie.
Hier ist die Implementierung eines einfachen Proxys, falls Sie vor dem Ausführen einer Funktion Code ausführen möchten.
quelle
isEnabled()
. A priori, aktiviert zu sein, ist internes Kochen vonFoo
, nicht ausgesetzt.Es gibt eine andere Lösung, die delegate (Zeiger auf Funktion) verwendet. Sie können eine eindeutige Methode haben, die zuerst die Validierung durchführt und dann die entsprechende Methode entsprechend der aufzurufenden Funktion (Parameter) aufruft. C # -Code:
quelle