This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface A { | |
String foo(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ADecorator implements A { | |
private final A delegate; | |
public ADecorator(A a) { | |
this.delegate = a; | |
} | |
public String foo() { | |
return "*** " + this.delegate.foo() + " ***"; //will return "*** foo ***" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class AImpl implements A { | |
public String foo() { | |
return "foo"; | |
} | |
} |
We can also use this pattern not just to decorate but also enhance with additional functionality (like in java.io.Reader implementations). Continuing our example above, with some changes:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface AB extends A { | |
String bar(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ABDecorator implements AB { | |
private final A delegate; | |
public ABDecorator(A a) { | |
this.delegate = a; | |
} | |
public String foo() { | |
return this.delegate.foo(); | |
} | |
public String bar() { | |
return "bar "; | |
} | |
} |
Enhancing Using Dynamic Proxy Class
A way to avoid all this work and verbose code is to use Dynamic Proxy Classes:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public abstract class ABFactory { | |
private ABFactory() { /* prevent instantiation */ } | |
public static AB createAB(A a) { | |
B b = new BImpl(a); | |
return (AB) Proxy.newProxyInstance(a.getClass().getClassLoader(), | |
new Class[]{AB.class}, (proxy, method, args) -> { | |
Class<?>[] declaringClassInterfaces = method.getDeclaringClass().getInterfaces(); | |
List<Class<?>> interfaces = new ArrayList<>(Arrays.asList(declaringClassInterfaces)); | |
interfaces.add(method.getDeclaringClass()); | |
if (interfaces.contains(A.class)) { | |
return method.invoke(a, args); | |
} else if (interfaces.contains(B.class)) { | |
return method.invoke(b, args); | |
} else { | |
throw new NoSuchMethodException("Could not find proxied method: " + | |
method.toGenericString()); | |
} | |
}); | |
} | |
private static class BImpl implements B { | |
private final A a; | |
private BImpl(A a) { | |
this.a = a; | |
} | |
@Override | |
public String bar() { | |
return a.foo() + "bar"; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
A a = new AImpl(); | |
... | |
AB ab = ABFactory.create(a); | |
System.out.println(ab.foo()); //prints "foo" | |
System.out.println(ab.bar()); //prints "foo & bar" |
In this example we define BImpl which is a private implementation of B - an interface with the extended functionality. BImpl takes an instance of A and encapsulates it so it can use it for its implementation. The create method creates a proxy object for interface AB (which is A & B). In the proxy, for each method we delegate to the corresponding object according to the origin class of the method.
Limitations
- This implementation relies on the knowledge that A and B are distinct - there are no shared methods. Otherwise we might delegate to the wrong object.
- Overriding a method from A in the proxy is possible but requires more effort to do it right and robust (mainly to method renaming).