[解決済み] クラス内のすべてのメソッドが特定のコードブロックから始まるようにするエレガントな方法はありますか?
質問
どのメソッドも同じように始まるクラスがあります。
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
を要求する(できれば毎回書かない)いい方法はないでしょうか?
fooIsEnabled
部分を要求する良い方法はありますか?
どのように解決するのですか?
エレガントかどうかは知りませんが、Javaの組み込みの
java.lang.reflect.Proxy
という
を強制する
に対するすべてのメソッド呼び出しが
Foo
をチェックすることから始まります。
enabled
の状態を確認します。
main
メソッドを使用します。
public static void main(String[] args) {
Foo foo = Foo.newFoo();
foo.setEnabled(false);
foo.bar(); // won't print anything.
foo.setEnabled(true);
foo.bar(); // prints "Executing method bar"
}
Foo
インターフェイスを使用します。
public interface Foo {
boolean getEnabled();
void setEnabled(boolean enable);
void bar();
void baz();
void bat();
// Needs Java 8 to have this convenience method here.
static Foo newFoo() {
FooFactory fooFactory = new FooFactory();
return fooFactory.makeFoo();
}
}
FooFactory
クラスがあります。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class FooFactory {
public Foo makeFoo() {
return (Foo) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler(new FooImpl()));
}
private static class FooImpl implements Foo {
private boolean enabled = false;
@Override
public boolean getEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enable) {
this.enabled = enable;
}
@Override
public void bar() {
System.out.println("Executing method bar");
}
@Override
public void baz() {
System.out.println("Executing method baz");
}
@Override
public void bat() {
System.out.println("Executing method bat");
}
}
private static class FooInvocationHandler implements InvocationHandler {
private FooImpl fooImpl;
public FooInvocationHandler(FooImpl fooImpl) {
this.fooImpl = fooImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Foo.class &&
!method.getName().equals("getEnabled") &&
!method.getName().equals("setEnabled")) {
if (!this.fooImpl.getEnabled()) {
return null;
}
}
return method.invoke(this.fooImpl, args);
}
}
}
他の人が指摘したように、心配するようなメソッドがほんの一握りしかない場合は、必要以上にやりすぎのように思えます。
とはいえ、確かに利点はあります。
-
ある種の懸念の分離が達成されます、なぜなら
Foo
のメソッドの実装がenabled
をチェックする必要はありません。その代わり、メソッドのコードはメソッドの主な目的が何であるかについてだけ心配すればよく、それ以上は必要ありません。 -
無実の開発者が、新しいメソッドを
Foo
クラスに新しいメソッドを追加し、誤ってenabled
をチェックします。 そのためenabled
チェックの動作は、新しく追加されたメソッドに自動的に継承されます。 -
別の横断的な関心事を追加する必要がある場合、あるいは
enabled
のチェックを強化する必要がある場合、一箇所で安全に行うことができ、非常に簡単です。 -
Java の組み込み機能でこの AOP のような動作が得られるのは、とても良いことです。のような他のフレームワークを統合することを強制されることはありません。
Spring
のような他のフレームワークを統合することを強制されることはありません。
公平に見て、デメリットもあります。
-
プロキシ呼び出しを処理する実装コードのいくつかは、醜いものです。のインスタンス化を防ぐためにインナークラスがあることも、 不快だと言う人もいるでしょう。
FooImpl
クラスのインスタンス化を防ぐために内部クラスを持つことは醜いという人もいるでしょう。 -
に新しいメソッドを追加したい場合
Foo
に新しいメソッドを追加したい場合、実装クラスとインターフェースの2箇所を変更する必要があります。 大したことではありませんが、それでも少し手間がかかります。 - プロキシ呼び出しは無料ではありません。一定のパフォーマンスオーバーヘッドがあります。しかし、一般的な使用では、それは顕著ではないでしょう。参照 を参照してください。 をご覧ください。
EDITです。
Fabian Streitelのコメントによって、私は上記のソリューションの2つの問題点について考えさせられました。
- 呼び出しハンドラでは、"getEnabled" と "setEnabled" メソッドで "enabled-check" をスキップするために魔法の文字列を使用します。これは、メソッド名がリファクタリングされた場合、簡単に壊れる可能性があります。
- もし、"enabled-check" の動作を継承しない新しいメソッドを追加する必要があるケースがあった場合、開発者がこれを間違ってしまうのはかなり簡単で、少なくとも、さらにマジック ストリングを追加することになるでしょう。
ポイント1を解決し、ポイント2の問題を少なくとも緩和するために、私はアノテーションを作成します。
BypassCheck
(または類似の何か) を作成し、それを使って
Foo
インターフェイスのメソッドをマークするために使用します。 この方法では、魔法の文字列はまったく必要なく、開発者がこの特別なケースで新しいメソッドを正しく追加することが非常に簡単になります。
アノテーションのソリューションを使用すると、コードは次のようになります。
main
メソッドを使用します。
public static void main(String[] args) {
Foo foo = Foo.newFoo();
foo.setEnabled(false);
foo.bar(); // won't print anything.
foo.setEnabled(true);
foo.bar(); // prints "Executing method bar"
}
BypassCheck
というアノテーションがあります。
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
インターフェイスを使用します。
public interface Foo {
@BypassCheck boolean getEnabled();
@BypassCheck void setEnabled(boolean enable);
void bar();
void baz();
void bat();
// Needs Java 8 to have this convenience method here.
static Foo newFoo() {
FooFactory fooFactory = new FooFactory();
return fooFactory.makeFoo();
}
}
FooFactory
クラスがあります。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class FooFactory {
public Foo makeFoo() {
return (Foo) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
new Class[]{Foo.class},
new FooInvocationHandler(new FooImpl()));
}
private static class FooImpl implements Foo {
private boolean enabled = false;
@Override
public boolean getEnabled() {
return this.enabled;
}
@Override
public void setEnabled(boolean enable) {
this.enabled = enable;
}
@Override
public void bar() {
System.out.println("Executing method bar");
}
@Override
public void baz() {
System.out.println("Executing method baz");
}
@Override
public void bat() {
System.out.println("Executing method bat");
}
}
private static class FooInvocationHandler implements InvocationHandler {
private FooImpl fooImpl;
public FooInvocationHandler(FooImpl fooImpl) {
this.fooImpl = fooImpl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Foo.class
&& !method.isAnnotationPresent(BypassCheck.class) // no magic strings
&& !this.fooImpl.getEnabled()) {
return null;
}
return method.invoke(this.fooImpl, args);
}
}
}
関連
-
JDKの設定時にjava.dllが見つからない、java SE Runtime Environmentが見つからない問題が発生しました。
-
HttpClientがGZIP形式でない場合の対処法
-
[解決済み] 整数の平方根が整数であるかどうかを判断する最速の方法
-
[解決済み] 特定のUnicode文字を含むコメントでのJavaコードの実行が許可されているのはなぜですか?
-
[解決済み] Eclipseにプロジェクトをインポートした後に「Must Override a Superclass Method」エラーが発生する。
-
[解決済み] IntelliJ: ワイルドカード・インポートを使用しない
-
[解決済み] メソッドの戻り値の型を汎用的にするにはどうすればよいですか?
-
[解決済み] ViewPagerとフラグメント - フラグメントの状態を保存する正しい方法は何ですか?
-
[解決済み] voidメソッドが例外を投げるかどうかをMockitoがテストする
-
[解決済み】関数型プログラミングはGoFデザインパターンに取って代わるか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
eclipse で「アクセス制限: タイプ 'HttpServer' は API ではありません」というプロンプトが表示される。
-
アクセス制限です。タイプ 'Application' は API ではありません。
-
Dateが型に解決できない問題を解決する
-
をインスタンス化することができません。
-
Android Studio 3.1.2 で v4, v7 パッケージが見つからない シンボル 'AppCompatActivity' を解決できない
-
this()の呼び出しはコンストラクタ本体の最初の文でなければならない 例外解決と原因分析
-
リソースの読み込みに失敗しました。サーバーはステータス500(内部サーバーエラー)で応答しました。
-
javaでクラスを作成すると、enclosing classでないように見える
-
Error: java.lang.NoClassDefFoundError: クラス XXXX を初期化できませんでした
-
SocketTimeoutExceptionです。読み込みがタイムアウトしました