Non conosco l'elegante, ma ecco un'implementazione funzionante che utilizza Java integrato java.lang.reflect.Proxy
che impone che tutte le invocazioni del metodo Foo
inizino controllando lo enabled
stato.
main
metodo:
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
interfaccia:
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);
}
}
}
Come altri hanno sottolineato, sembra eccessivo per quello che ti serve se hai solo una manciata di metodi di cui preoccuparti.
Detto questo, ci sono sicuramente dei vantaggi:
- Si ottiene una certa separazione delle preoccupazioni, poiché
Foo
le implementazioni del metodo non devono preoccuparsi della preoccupazione enabled
trasversale del controllo. Invece, il codice del metodo deve solo preoccuparsi di quale sia lo scopo principale del metodo, niente di più.
- Non è possibile per uno sviluppatore innocente aggiungere un nuovo metodo alla
Foo
classe e "dimenticare" erroneamente di aggiungere il enabled
controllo. Il enabled
comportamento del controllo viene ereditato automaticamente da qualsiasi metodo appena aggiunto.
- Se è necessario aggiungere un'altra preoccupazione trasversale o se è necessario migliorare il
enabled
controllo, è molto facile farlo in modo sicuro e in un unico posto.
- È un po 'bello poter ottenere questo comportamento simile ad AOP con funzionalità Java integrata. Non sei obbligato a dover integrare qualche altro framework come
Spring
, anche se possono sicuramente essere buone opzioni.
Ad essere onesti, alcuni degli aspetti negativi sono:
- Parte del codice di implementazione che gestisce le chiamate proxy è brutto. Alcuni direbbero anche che avere classi interne per prevenire l'istanza della
FooImpl
classe è brutto.
- Se si desidera aggiungere un nuovo metodo a
Foo
, è necessario apportare una modifica in 2 punti: la classe di implementazione e l'interfaccia. Non è un grosso problema, ma è ancora un po 'più di lavoro.
- Le chiamate proxy non sono gratuite. C'è un certo sovraccarico di prestazioni. Per uso generale, tuttavia, non sarà evidente. Vedi qui per maggiori informazioni.
MODIFICARE:
Il commento di Fabian Streitel mi ha fatto pensare a 2 fastidi con la mia soluzione di cui sopra, lo ammetto, non sono contento di me stesso:
- Il gestore di invocazione utilizza stringhe magiche per saltare il "abilitato-controllo" sui metodi "getEnabled" e "setEnabled". Questo può facilmente rompersi se i nomi dei metodi vengono refactored.
- Se si verificasse un caso in cui è necessario aggiungere nuovi metodi che non dovrebbero ereditare il comportamento di "controllo abilitato", allora può essere abbastanza facile per lo sviluppatore sbagliare questo, e almeno significherebbe aggiungere più magia stringhe.
Per risolvere il punto n. 1 e almeno per alleviare il problema con il punto n. 2, creerei un'annotazione BypassCheck
(o qualcosa di simile) che potrei usare per contrassegnare i metodi Foo
nell'interfaccia per i quali non voglio eseguire il " controllo abilitato ". In questo modo, non ho affatto bisogno di stringhe magiche e diventa molto più facile per uno sviluppatore aggiungere correttamente un nuovo metodo in questo caso speciale.
Utilizzando la soluzione di annotazione, il codice sarebbe simile al seguente:
main
metodo:
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
annotazione:
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BypassCheck {
}
Foo
interfaccia:
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);
}
}
}