I don't know about elegant, but here is a working implementation using Java's built-in java.lang.reflect.Proxy that enforces that all method invocations on Foo begin by checking the enabled state.
main method:
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 interface:
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 class:
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);
}
}
}
As others have pointed out, it does seem like overkill for what you need if you only have a handful of methods to worry about.
That said, there certainly are benefits:
- A certain separation of concerns is achieved, because
Foo's method implementations don't have to worry about the enabled check cross-cutting concern. Instead, the method's code only needs to worry about what the method's primary purpose is, nothing more.
- There is no way for an innocent developer to add a new method to the
Foo class and mistakenly "forget" to add the enabled check. The enabled check behavior is automatically inherited by any newly added method.
- If you need to add another cross-cutting concern, or if you need to enhance the
enabled check, it's very easy to do so safely and in one place.
- It is kind of nice that you can get this AOP-like behavior with built-in Java functionality. You are not forced into having to integrate some other framework like
Spring, though they can definitely be good options too.
To be fair, some of the downsides are:
- Some of the implementation code that handles the proxy invocations is ugly. Some would also say that having inner classes to prevent instantiation of the
FooImpl class is ugly.
- If you want to add a new method to
Foo, you have to make a change in 2 spots: the implementation class and the interface. Not a big deal, but it's still a bit more work.
- Proxy invocations are not free. There is a certain performance overhead. For general use though, it won't be noticeable. See here for more information.
EDIT:
Fabian Streitel's comment got me thinking about 2 annoyances with my above solution that, I'll admit, I'm not happy about myself:
- The invocation handler uses magic strings to skip the "enabled-check" on the "getEnabled" and "setEnabled" methods. This can easily break if the method names are refactored.
- If there was a case where new methods need to be added that should not inherit the "enabled-check" behavior, then it can be pretty easy for the developer to get this wrong, and at the very least, it would mean adding more magic strings.
To resolve point #1, and to at least ease the problem with point #2, I would create an annotation BypassCheck (or something similar) that I could use to mark the methods in the Foo interface for which I don't want to perform the "enabled check". This way, I don't need magic strings at all, and it becomes a lot easier for a developer to correctly add a new method in this special case.
Using the annotation solution, the code would look like this:
main method:
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 annotation:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo interface:
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 class:
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);
}
}
}