Je ne sais pas à propos d'élégant, mais voici une implémentation fonctionnelle utilisant la fonction intégrée de Java java.lang.reflect.Proxy
qui impose que toutes les invocations de méthode Foo
commencent par vérifier l' enabled
état.
main
méthode:
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);
}
}
}
Comme d'autres l'ont souligné, il semble exagéré pour ce dont vous avez besoin si vous n'avez que quelques méthodes à craindre.
Cela dit, il y a certainement des avantages:
- Une certaine séparation des préoccupations est obtenue, car
Foo
les implémentations de méthodes n'ont pas à se soucier de la enabled
préoccupation transversale de vérification. Au lieu de cela, le code de la méthode n'a qu'à se soucier de l'objectif principal de la méthode, rien de plus.
- Il n'y a aucun moyen pour un développeur innocent d'ajouter une nouvelle méthode à la
Foo
classe et par erreur "oublier" d'ajouter la enabled
vérification. Le enabled
comportement de vérification est automatiquement hérité par toute méthode nouvellement ajoutée.
- Si vous avez besoin d'ajouter une autre préoccupation transversale, ou si vous avez besoin d'améliorer le
enabled
contrôle, il est très facile de le faire en toute sécurité et en un seul endroit.
- C'est plutôt bien que vous puissiez obtenir ce comportement de type AOP avec la fonctionnalité Java intégrée. Vous n'êtes pas obligé d'avoir à intégrer un autre framework comme
Spring
, bien qu'ils puissent certainement être de bonnes options aussi.
Pour être juste, certains des inconvénients sont:
- Une partie du code d'implémentation qui gère les invocations de proxy est laide. Certains diront aussi qu'avoir des classes internes pour empêcher l'instanciation de la
FooImpl
classe est moche.
- Si vous souhaitez ajouter une nouvelle méthode à
Foo
, vous devez effectuer un changement en 2 points: la classe d'implémentation et l'interface. Pas grand-chose, mais c'est encore un peu plus de travail.
- Les invocations de proxy ne sont pas gratuites. Il y a une certaine surcharge de performance. Pour un usage général cependant, cela ne sera pas perceptible. Voir ici pour plus d' informations.
ÉDITER:
Le commentaire de Fabian Streitel m'a fait réfléchir à 2 ennuis avec ma solution ci-dessus qui, je l'admets, je ne suis pas content de moi:
- Le gestionnaire d'invocation utilise des chaînes magiques pour ignorer le «contrôle activé» sur les méthodes «getEnabled» et «setEnabled». Cela peut facilement casser si les noms de méthode sont refactorisés.
- S'il y avait un cas où de nouvelles méthodes devaient être ajoutées qui ne devraient pas hériter du comportement "enabled-check", alors il peut être assez facile pour le développeur de se tromper, et à tout le moins, cela signifierait ajouter plus de magie cordes.
Pour résoudre le point n ° 1, et pour au moins atténuer le problème avec le point n ° 2, je créerais une annotation BypassCheck
(ou quelque chose de similaire) que je pourrais utiliser pour marquer les méthodes de l' Foo
interface pour lesquelles je ne veux pas exécuter le " vérification activée ". De cette façon, je n'ai pas du tout besoin de chaînes magiques, et il devient beaucoup plus facile pour un développeur d'ajouter correctement une nouvelle méthode dans ce cas particulier.
En utilisant la solution d'annotation, le code ressemblerait à ceci:
main
méthode:
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
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);
}
}
}