Eu não sei sobre o elegante, mas aqui está uma implementação de trabalho usando o Java embutido java.lang.reflect.Proxy
que impõe que todas as invocações de métodos emFoo
começam verificando o 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
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
classe:
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 outros já apontaram, parece um exagero para o que você precisa se você tiver apenas alguns métodos para se preocupar.
Dito isto, certamente existem benefícios:
- Uma certa separação de preocupações é alcançada, porque
Foo
as implementações do método não precisam se preocupar com a preocupação enabled
transversal da verificação. Em vez disso, o código do método precisa se preocupar apenas com o objetivo principal do método, nada mais.
- Não há como um desenvolvedor inocente adicionar um novo método à
Foo
classe e, por engano, "esquecer" de adicionar a enabled
verificação. O enabled
comportamento da verificação é herdado automaticamente por qualquer método adicionado recentemente.
- Se você precisar adicionar outra preocupação transversal ou melhorar a
enabled
verificação, é muito fácil fazê-lo com segurança e em um só lugar.
- É bom que você possa obter esse comportamento semelhante ao AOP com a funcionalidade Java integrada. Você não é obrigado a integrar outra estrutura como
Spring
, embora elas também possam ser boas opções.
Para ser justo, algumas das desvantagens são:
- Parte do código de implementação que lida com as invocações de proxy é feio. Alguns diriam também que ter classes internas para impedir a instanciação do
FooImpl
classe é feio.
- Se você deseja adicionar um novo método ao
Foo
, é necessário fazer uma alteração em 2 pontos: a classe de implementação e a interface. Não é grande coisa, mas ainda é um pouco mais de trabalho.
- As invocações de proxy não são gratuitas. Há uma certa sobrecarga de desempenho. Para uso geral, porém, não será perceptível. Veja aqui para mais informações.
EDITAR:
O comentário de Fabian Streitel me fez pensar em 2 aborrecimentos com a minha solução acima que, admito, não estou feliz comigo mesma:
- O manipulador de chamada usa seqüências de caracteres mágicas para ignorar a "verificação ativada" nos métodos "getEnabled" e "setEnabled". Isso pode ser interrompido facilmente se os nomes dos métodos forem refatorados.
- Se houver um caso em que novos métodos precisem ser adicionados que não herdem o comportamento de "verificação habilitada", pode ser bem fácil para o desenvolvedor cometer um erro e, no mínimo, isso significaria adicionar mais magia cordas.
Para resolver o ponto 1 e, pelo menos, aliviar o problema com o ponto 2, eu criaria uma anotação BypassCheck
(ou algo semelhante) que poderia ser usada para marcar os métodos na Foo
interface para os quais não quero executar o " verificação ativada ". Dessa forma, não preciso de seqüências de caracteres mágicas e fica muito mais fácil para um desenvolvedor adicionar corretamente um novo método neste caso especial.
Usando a solução de anotação, o código ficaria assim:
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
anotação:
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
classe:
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);
}
}
}