私はエレガントについて知っているが、ここで使用して働いて実装されていない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
チェックを強化する必要がある場合は、安全かつ1か所で簡単に実行できます。
- 組み込みのJava機能を使用して、このAOPのような動作を実現できるのは、ちょっといいことです。のような他のフレームワークを統合する必要はありませんが
Spring
、これらも間違いなく優れたオプションです。
公平に言うと、いくつかの欠点があります。
- プロキシの呼び出しを処理する実装コードの一部は醜いです。クラスのインスタンス化を防ぐために内部クラスを持つこと
FooImpl
は醜いと言う人もいます。
- 新しいメソッドをに追加する
Foo
場合は、実装クラスとインターフェースの2つの場所を変更する必要があります。大したことではありませんが、それでもまだ少し作業が必要です。
- プロキシ呼び出しは無料ではありません。特定のパフォーマンスのオーバーヘッドがあります。ただし、一般的な使用では目立ちません。詳細については、こちらをご覧ください。
編集:
Fabian Streitelのコメントにより、私は上記の解決策について2つの問題について考えるようになりました。それは認めますが、自分自身については不満です。
- 呼び出しハンドラは、マジックストリングを使用して、「getEnabled」および「setEnabled」メソッドの「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);
}
}
}