Java – how to create a class that I can’t change and implement an interface?
I have a class from another closed source library, but I want to be able to use its interface The reason is that I don't want to do instanceof checking or null checking anywhere, but I don't want to extend existing classes
For example, suppose I have such code:
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(); } }
I can't change quietfoo:
public class QuietFoo { public void bar() { System.out.println("bar"); } }
But I can change loudfoo:
public class LoudFoo { public void bar() { System.out.println("BAR!!"); } }
The problem is that there may be many other bar implementations in many classes, and there may be more methods than just bar, so not only will my handle method become slow and ugly due to a large number of instanceof statements, but I have to write these methods to deal with each method on quietfoo and loudfoo Extension is not a viable solution because it violates the entire contract because loudfoo is not quiet foo
Basically, give foo:
public interface Foo { void bar(); }
How can quietfoo implement foo without changing the source so that I don't have to convert and instanceof call anywhere in the code?
Solution
There are two methods:
>Use adapter mode > use proxy
The adapter method will be simpler but less flexible, and the proxy method will be more complex but more flexible Although proxy methods are more complex, this complexity is limited to a few classes
Adapter
The adapter pattern is very simple For your example, it is just a class, as follows:
public class QuietFooAdapter implements Foo { private QuietFoo quietFoo; public QuietFooAdapter(QuietFoo quietFoo) { this.quietFoo = quietFoo; } public void bar() { quietFoo.bar(); } }
Then use it:
Foo foo = new QuietFooAdapter(new QuietFoo()); foo.bar();
This is good, but if you have more than one class to make adapters, it can be tedious because you need to add a new adapter for each class that must be wrapped
Java proxy class
Proxy is a native Java class that is part of the reflection library and allows you to create more general reflection solutions It involves three parts:
>Interface (in this case, foo) > invocationhandler > create proxy (proxy. Newproxyinstance)
We already have an interface, so we're good there
Invocationhandler is where we "automatically adapt" through reflection:
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(); } } }
The important code here is in the try block This handles the process of adapting any method calls called on the proxy to the internal target object If a method is called on an unsupported interface (non-public, wrong return type, or just does not exist), we throw unsupported operationexception If we catch invocationtargetexception, we will use invocationtargetexception Gettargetexception re throws the exception that caused it This happens when the method we call throws an exception Java wraps it in a new exception and throws the new exception
Next, we need something to create the adapter:
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)); } }
If you like, you can also nest the adapterinvocationhandler class in the adapterfactory class so that everything in the adapterfactory is self-contained
Then use it:
Foo foo = AdapterFactory.createAdapter(new QuietFoo(),Foo.class); foo.bar();
This method requires more code than implementing a single adapter, but it is generic enough to create automatic adapters for any class and interface pair, not just quietfoo and foo examples Of course, this method uses reflection (the proxy class uses reflection, and so does our invocationhandler), which may be slower, but recent improvements in the JVM make reflection much faster than before