No sé acerca de elegante, pero aquí hay una implementación funcional que usa el Java incorporado java.lang.reflect.Proxyque exige que todas las invocaciones de métodos Foocomiencen por verificar el enabledestado.
main método:
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 interfaz:
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 clase:
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);
}
}
}
Como otros han señalado, parece excesivo lo que necesita si solo tiene que preocuparse por un puñado de métodos.
Dicho esto, ciertamente hay beneficios:
- Se logra una cierta separación de preocupaciones, porque
Foolas implementaciones de métodos no tienen que preocuparse por la preocupación enabledtransversal de la verificación. En cambio, el código del método solo necesita preocuparse sobre cuál es el propósito principal del método, nada más.
- No hay forma de que un desarrollador inocente agregue un nuevo método a la
Fooclase y, por error, "olvide" agregar el enabledcheque. El enabledcomportamiento de verificación se hereda automáticamente por cualquier método recién agregado.
- Si necesita agregar otra preocupación transversal, o si necesita mejorar la
enabledverificación, es muy fácil hacerlo de manera segura y en un solo lugar.
- Es agradable que pueda obtener este comportamiento similar a AOP con la funcionalidad Java incorporada. No está obligado a tener que integrar algún otro marco como
Spring, aunque definitivamente también pueden ser buenas opciones.
Para ser justos, algunos de los inconvenientes son:
- Parte del código de implementación que maneja las invocaciones de proxy es feo. Algunos también dirían que tener clases internas para evitar la creación de instancias de la
FooImplclase es feo.
- Si desea agregar un nuevo método
Foo, debe realizar un cambio en 2 puntos: la clase de implementación y la interfaz. No es un gran problema, pero sigue siendo un poco más de trabajo.
- Las invocaciones de proxy no son gratuitas. Hay una cierta sobrecarga de rendimiento. Sin embargo, para uso general, no se notará. Ver aquí para más información.
EDITAR:
El comentario de Fabian Streitel me hizo pensar en 2 molestias con mi solución anterior que, admito, no estoy contento conmigo mismo:
- El controlador de invocación utiliza cadenas mágicas para omitir la "comprobación habilitada" en los métodos "getEnabled" y "setEnabled". Esto puede romperse fácilmente si se refactorizan los nombres de los métodos.
- Si hubo un caso en el que se deben agregar nuevos métodos que no hereden el comportamiento de "verificación habilitada", entonces puede ser bastante fácil para el desarrollador equivocarse, y al menos, significaría agregar más magia instrumentos de cuerda.
Para resolver el punto n. ° 1 y, al menos, aliviar el problema con el punto n. ° 2, crearía una anotación BypassCheck(o algo similar) que podría utilizar para marcar los métodos en la Foointerfaz para los que no quiero realizar el " verificación habilitada ". De esta manera, no necesito cadenas mágicas en absoluto, y se vuelve mucho más fácil para un desarrollador agregar correctamente un nuevo método en este caso especial.
Usando la solución de anotación, el código se vería así:
main método:
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 anotación:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo interfaz:
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 clase:
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);
}
}
}