No sé acerca de elegante, pero aquí hay una implementación funcional que usa el Java incorporado java.lang.reflect.Proxy
que exige que todas las invocaciones de métodos Foo
comiencen por verificar el enabled
estado.
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
Foo
las implementaciones de métodos no tienen que preocuparse por la preocupación enabled
transversal 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
Foo
clase y, por error, "olvide" agregar el enabled
cheque. El enabled
comportamiento 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
enabled
verificació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
FooImpl
clase 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 Foo
interfaz 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);
}
}
}