8

クローズド ソースの別のライブラリのクラスがありますが、そのインターフェイスを使用できるようにしたいと考えています。その理由は、どこでもinstanceofチェックやチェックをしたくないからですが、既存のクラスを拡張したくないからです。null

たとえば、次のコードがあるとします。

public class Example {

    // QuietFoo is from another library that I can't change
    private static QuietFoo quietFoo;
    // LoudFoo is my own code and is meant to replace QuietFoo
    private static LoudFoo loudFoo;

    public static void main(String[] args) {
        handle(foo);
    }

    private static void handle(Object foo) {
        if (foo instanceof QuietFoo)
            ((QuietFoo) foo).bar();
        else if (foo instanceof LoudFoo)
            ((LoudFoo) foo).bar();
    }
}

変更できませんQuietFoo:

public class QuietFoo {

    public void bar() {
        System.out.println("bar");
    }
}

しかし、私変更できますLoudFoo

public class LoudFoo {

    public void bar() {
        System.out.println("BAR!!");
    }
}

問題は、多くのクラスに の他の多くの実装が存在する可能性があり、 だけでなくbarより多くのメソッドが存在する可能性があるため、多くのステートメントでメソッドが遅くなり醜くなるだけでなく、これらのハンドル メソッドの 1 つを作成する必要があることです。およびの各メソッドについて。拡張は実行可能な解決策ではありません。これは is-a ではないため、 is-a契約全体に違反するためです。barhandleinstanceofQuietFooLoudFooLoudFooQuietFoo

基本的に、与えられたFoo

public interface Foo {
    void bar();
}

コードのどこでもキャストや呼び出しを行う必要がないように、ソースを変更せずにQuietFoo実装するにはどうすればよいですか?Fooinstanceof

4

1 に答える 1

15

次の 2 つの方法があります。

  1. アダプター パターンの使用
  2. 使用するProxy

アダプター アプローチは単純ですが柔軟性が低く、Proxyアプローチはより複雑ですが柔軟性が高くなります。アプローチはより複雑ですProxyが、その複雑さはすべていくつかのクラスに制限されています。


アダプタ

アダプターのパターンは単純です。あなたの例では、次のように1つのクラスになります。

public class QuietFooAdapter implements Foo {

    private QuietFoo quietFoo;

    public QuietFooAdapter(QuietFoo quietFoo) {
        this.quietFoo = quietFoo;
    }

    public void bar() {
        quietFoo.bar();
    }
}

それを使用するには:

Foo foo = new QuietFooAdapter(new QuietFoo());
foo.bar();

これは良いことですが、アダプターを作成するクラスが複数ある場合は、ラップする必要があるクラスごとに新しいアダプターが必要になるため、面倒な場合があります。


JavaのProxyクラス

Proxyは、リフレクション ライブラリの一部であるネイティブ Java クラスであり、より一般的なリフレクティブ ソリューションを作成できます。これには 3 つの部分が含まれます。

  1. インターフェイス (この場合はFoo)
  2. InvocationHandler
  3. プロキシの作成 ( Proxy.newProxyInstance)

インターフェースはすでにあるので、問題ありません。

これInvocationHandlerは、リフレクションを介して「自動適応」を行う場所です。

public class AdapterInvocationHandler implements InvocationHandler {

    private Object target;
    private Class<?> targetClass;

    public AdapterInvocationHandler(Object target) {
        this.target = target;
        targetClass = target.getClass();
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            Method targetMethod = targetClass.getMethod(method.getName(), method.getParameterTypes());
            if (!method.getReturnType().isAssignableFrom(targetMethod.getReturnType()))
                throw new UnsupportedOperationException("Target (" + target.getClass().getName() + ") does not support: " + method.toGenericString());
            return targetMethod.invoke(target, args);
        } catch (NoSuchMethodException ex) {
            throw new UnsupportedOperationException("Target (" + target.getClass().getName() + ") does not support: " + method.toGenericString());
        } catch (IllegalAccessException ex) {
            throw new UnsupportedOperationException("Target (" + target.getClass().getName() + ") does not declare method to be public: " + method.toGenericString());
        } catch (InvocationTargetException ex) {
            // May throw a NullPointerException if there is no target exception
            throw ex.getTargetException();
        }
    }
}

ここで重要なコードはtryブロックにあります。これにより、プロキシで呼び出されたメソッド呼び出しを内部targetオブジェクトに適合させるプロセスが処理されます。サポートされていないインターフェイスでメソッドが呼び出された場合 (非public、間違った戻り値の型、またはフラット アウトが存在しない)、 をスローしUnsupportedOperationExceptionます。をキャッチするInvocationTargetExceptionと、それを引き起こした例外を 経由で再スローしますInvocationTargetException.getTargetException。これは、反射的に呼び出したメソッドが例外をスローしたときに発生します。Java はそれを新しい例外でラップし、その新しい例外をスローします。

次に、アダプターを作成するために何かが必要です。

public class AdapterFactory {

    public static <T> T createAdapter(Object target, Class<T> interfaceClass) {
        if (!interfaceClass.isInterface())
            throw new IllegalArgumentException("Must be an interface: " + interfaceClass.getName());
        return (T) Proxy.newProxyInstance(null, new Class<?>[] { interfaceClass }, new AdapterInvocationHandler(target));
    }
}

AdapterInvocationHandler必要に応じて、クラスをクラスにネストしてAdapterFactory、すべてが自己完結型になるようにすることもできますAdapterFactory

それを使用するには:

Foo foo = AdapterFactory.createAdapter(new QuietFoo(), Foo.class);
foo.bar();

QuietFooこのアプローチは、単一のアダプターを実装するよりも多くのコードを必要としますが、 andの例だけでなく、任意のクラスとインターフェイスのペアの自動アダプターを作成するために使用できるほど十分に汎用的Fooです。確かに、このメソッドはリフレクションを使用します (Proxyクラスはリフレクションを使用し、私たちの と同様InvocationHandler)、これは遅くなる可能性がありますが、JVM の最近の改善により、リフレクションは以前よりもはるかに高速になりました。

于 2012-11-13T20:33:42.760 に答える