J'ai une classe où chaque méthode commence de la même manière:
class Foo {
public void bar() {
if (!fooIsEnabled) return;
//...
}
public void baz() {
if (!fooIsEnabled) return;
//...
}
public void bat() {
if (!fooIsEnabled) return;
//...
}
}
Existe-t-il un moyen agréable d'exiger (et, espérons-le, de ne pas écrire à chaque fois) la fooIsEnabled
partie pour chaque méthode publique de la classe?
java
design-patterns
Kristina
la source
la source
Réponses:
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éthodeFoo
commencent par vérifier l'enabled
état.main
méthode:Foo
interface:FooFactory
classe: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:
Foo
les implémentations de méthodes n'ont pas à se soucier de laenabled
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.Foo
classe et par erreur "oublier" d'ajouter laenabled
vérification. Leenabled
comportement de vérification est automatiquement hérité par toute méthode nouvellement ajoutée.enabled
contrôle, il est très facile de le faire en toute sécurité et en un seul endroit.Spring
, bien qu'ils puissent certainement être de bonnes options aussi.Pour être juste, certains des inconvénients sont:
FooImpl
classe est moche.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.É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:
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:BypassCheck
annotation:Foo
interface:FooFactory
classe:la source
Il y a beaucoup de bonnes suggestions ... ce que vous pouvez faire pour résoudre votre problème est de penser au modèle d'état et de l'implémenter.
Jetez un œil à cet extrait de code ... peut-être qu'il vous donnera une idée. Dans ce scénario, il semble que vous souhaitiez modifier l'ensemble de l'implémentation des méthodes en fonction de l'état interne de l'objet. Veuillez vous rappeler que la somme des méthodes dans un objet est connue sous le nom de comportement.
J'espère que vous aimez!
PD: est une mise en œuvre du modèle d'état (également connu sous le nom de stratégie en fonction du contexte ... mais les principes sont les mêmes).
la source
bar()
inFooEnabledBehavior
etbar()
inFooDisabledBehavior
pourraient partager une grande partie du même code, avec peut-être même une seule ligne différente entre les deux. Vous pourriez très facilement, surtout si ce code devait être maintenu par des développeurs juniors (comme moi), vous retrouver avec un énorme bordel de merde qui est impossible à maintenir et à tester. Cela peut arriver avec n'importe quel code, mais cela semble tellement facile à foutre très vite. +1 cependant, car bonne suggestion.Oui, mais c'est un peu de travail, cela dépend donc de son importance pour vous.
Vous pouvez définir la classe en tant qu'interface, écrire une implémentation de délégué, puis l'utiliser
java.lang.reflect.Proxy
pour implémenter l'interface avec des méthodes qui effectuent la partie partagée, puis appellent conditionnellement le délégué.Votre
MyInvocationHandler
peut ressembler à quelque chose comme ceci (gestion des erreurs et échafaudage de classe omis, en supposant qu'ilfooIsEnabled
soit défini dans un endroit accessible):Ce n'est pas incroyablement joli. Mais contrairement à divers commentateurs, je le ferais, car je pense que la répétition est un risque plus important que ce type de densité, et vous serez en mesure de produire la "sensation" de votre vraie classe, avec ce wrapper quelque peu impénétrable ajouté. très localement en seulement quelques lignes de code.
Consultez la documentation Java pour plus de détails sur les classes proxy dynamiques.
la source
Cette question est étroitement liée à la programmation orientée aspect . AspectJ est une extension AOP de Java et vous pouvez lui donner un coup d'oeil pour obtenir une certaine inspiration.
Autant que je sache, il n'y a pas de support direct pour AOP en Java. Il y a quelques modèles GOF qui s'y rapportent, comme par exemple la méthode de modèle et la stratégie, mais cela ne vous sauvera pas vraiment des lignes de code.
En Java et dans la plupart des autres langages, vous pouvez définir la logique récurrente dont vous avez besoin dans les fonctions et adopter une approche de codage dite disciplinée dans laquelle vous les appelez au bon moment.
Cependant, cela ne conviendrait pas à votre cas car vous souhaitez que le code factorisé puisse revenir
checkBalance
. Dans les langages qui prennent en charge les macros (comme C / C ++), vous pouvez définircheckSomePrecondition
et encheckSomePostcondition
tant que macros et elles seraient simplement remplacées par le préprocesseur avant même que le compilateur ne soit appelé:Java ne l'a pas prêt à l'emploi. Cela peut offenser quelqu'un, mais j'ai utilisé la génération automatique de code et des moteurs de modèles pour automatiser les tâches de codage répétitives dans le passé. Si vous traitez vos fichiers Java avant de les compiler avec un préprocesseur approprié, par exemple Jinja2, vous pouvez faire quelque chose de similaire à ce qui est possible en C.
Approche Java pure possible
Si vous recherchez une solution Java pure, ce que vous pouvez trouver ne sera probablement pas concis. Mais cela pourrait toujours éliminer les parties communes de votre programme et éviter la duplication de code et les bogues. Vous pouvez faire quelque chose comme ça (c'est une sorte de modèle inspiré de la stratégie ). Notez qu'en C # et Java 8, et dans d'autres langages dans lesquels les fonctions sont un peu plus faciles à gérer, cette approche peut en fait sembler agréable.
Un peu d'un scénario du monde réel
Vous développez une classe pour envoyer des trames de données à un robot industriel. Le robot prend du temps pour exécuter une commande. Une fois la commande terminée, elle vous renvoie une trame de contrôle. Le robot peut être endommagé s'il reçoit une nouvelle commande alors que la précédente est toujours en cours d'exécution. Votre programme utilise une
DataLink
classe pour envoyer et recevoir des trames vers et depuis le robot. Vous devez protéger l'accès à l'DataLink
instance.Les appels de fil d'interface utilisateur
RobotController.left
,right
,up
oudown
lorsque l'utilisateur clique sur les boutons, mais appelle égalementBaseController.tick
à intervalles réguliers, afin de la transmission de commande réactivez au secteur privé parDataLink
exemple.la source
protect
qui est plus facile à réutiliser et à documenter. Si vous dites au futur responsable avec lequel le code critique doit être protégéprotect
, vous lui dites déjà quoi faire. Si les règles de protection changent, le nouveau code est toujours protégé. C'est exactement la raison d'être de la définition des fonctions, mais l'OP avait besoin de «renvoyer unreturn
», ce que les fonctions ne peuvent pas faire.J'envisagerais de refactoriser. Ce modèle rompt fortement le modèle DRY (ne vous répétez pas). Je crois que cela brise cette responsabilité de classe. Mais cela dépend de votre maîtrise du code. Votre question est très ouverte - où appelez-vous
Foo
instance?Je suppose que vous avez du code comme
peut-être devriez-vous l'appeler de cette façon:
Et gardez-le propre. Par exemple, la journalisation:
a
logger
ne se demande pas si le débogage est activé. Il enregistre simplement.Au lieu de cela, la classe appelante doit vérifier ceci:
S'il s'agit d'une bibliothèque et que vous ne pouvez pas contrôler l'appel de cette classe, lancez un
IllegalStateException
qui explique pourquoi, si cet appel est illégal et cause des problèmes.la source
À mon humble avis, la solution la plus élégante et la plus performante à cela consiste à avoir plus d'une implémentation de Foo, ainsi qu'une méthode d'usine pour en créer une:
Ou une variante:
Comme le souligne sstan, il serait encore mieux d'utiliser une interface:
Adaptez cela au reste de vos circonstances (créez-vous un nouveau Foo à chaque fois ou réutilisez-vous la même instance, etc.)
la source
Foo
et d'oublier d'ajouter la version no-op de la méthode dans la classe étendue, contournant ainsi le comportement souhaité.isFooEnabled
lors de l'instanciation deFoo
. C'est trop tôt. Dans le code d'origine, cela se fait lorsqu'une méthode est exécutée. La valeur deisFooEnabled
peut changer entre-temps.fooIsEnabled
peut être constant. Mais rien ne nous dit que ce soit constant. Je considère donc le cas général.J'ai une autre approche: avoir un
}
et alors tu peux faire
Peut-être que vous pouvez même remplacer le
isFooEnabled
par uneFoo
variable qui contient leFooImpl
à utiliser ou leNullFoo.DEFAULT
. Ensuite, l'appel est à nouveau plus simple:BTW, cela s'appelle le "modèle nul".
la source
(isFooEnabled ? foo : NullFoo.DEFAULT).bar();
semble un peu maladroite. Avoir une troisième implémentation qui délègue à l'une des implémentations existantes. Au lieu de changer la valeur d'un champ,isFooEnabled
la cible de la délégation pourrait être modifiée. Cela réduit le nombre de branches dans le codeFoo
dans le code d'appel! Comment pourrions-nous savoir siisFooEnabled
? Ceci est un champ interne de la classeFoo
.Dans une approche fonctionnelle similaire à la réponse de @ Colin, avec les fonctions lambda de Java 8 , il est possible d'envelopper le code d'activation / désactivation de la fonction conditionnelle dans une méthode de garde (
executeIfEnabled
) qui accepte l'action lambda, à laquelle le code à exécuter conditionnellement peut être passé.Bien que dans votre cas, cette approche ne sauvera aucune ligne de code, en séchant cela, vous avez maintenant la possibilité de centraliser d'autres problèmes de basculement de fonctionnalités, ainsi que des problèmes AOP ou de débogage tels que la journalisation, les diagnostics, le profilage, etc.
Un avantage de l'utilisation de lambdas ici est que les fermetures peuvent être utilisées pour éviter d'avoir à surcharger la
executeIfEnabled
méthode.Par exemple:
Avec un test:
la source
Comme indiqué dans d'autres réponses, le modèle de conception de stratégie est un modèle de conception approprié à suivre pour simplifier ce code. Je l'ai illustré ici en utilisant l'invocation de méthode par réflexion, mais il existe un certain nombre de mécanismes que vous pouvez utiliser pour obtenir le même effet.
la source
Proxy
.foo.fooIsEnabled ...
? A priori, c'est un champ interne de l'objet, on ne peut pas, et on ne veut pas, le voir à l'extérieur.Si seulement Java était un peu meilleur pour être fonctionnel. Il pense que la solution la plus OOO est de créer une classe qui encapsule une seule fonction, elle n'est donc appelée que lorsque foo est activé.
puis implémenter
bar
,baz
et enbat
tant que classes anonymes qui s'étendentFunctionWrapper
.Une autre idée
Utilisez la solution nullable de glglgl mais les classes make
FooImpl
etNullFoo
internes (avec des constructeurs privés) de la classe ci-dessous:de cette façon, vous n'avez pas à vous soucier de vous souvenir d'utiliser
(isFooEnabled ? foo : NullFoo.DEFAULT)
.la source
Foo foo = new Foo()
pour appelerbar
vous écririezfoo.bar.call()
Il semble que la classe ne fasse rien lorsque Foo n'est pas activé, alors pourquoi ne pas l'exprimer à un niveau supérieur où vous créez ou obtenez l'instance Foo?
Cela ne fonctionne que si isFooEnabled est une constante. Dans un cas général, vous pouvez créer votre propre annotation.
la source
fooIsEnabled
une méthode est appelée. Vous faites cela avant l'instanciation deFoo
. C'est trop tôt. La valeur peut changer entre-temps.isFooEnabled
est un champ d'instance d'Foo
objets.Je ne connais pas la syntaxe Java. Supposons qu'en Java, il y a polymorphisme, propriété statique, classe abstraite et méthode:
la source
new bar()
dire?Name
avec une majuscule.bar()
. Si vous avez besoin de modifier cela, vous êtes condamné.Fondamentalement, vous avez un indicateur qui, s'il est défini, l'appel de fonction doit être ignoré. Je pense donc que ma solution serait idiote, mais la voici.
Voici l'implémentation d'un proxy simple, au cas où vous voudriez exécuter du code avant d'exécuter une fonction.
la source
isEnabled()
. A priori, l' activation est une cuisson interneFoo
, non exposée.Il existe une autre solution, en utilisant le délégué (pointeur sur la fonction). Vous pouvez avoir une méthode unique qui effectue d'abord la validation, puis appelle la méthode appropriée en fonction de la fonction (paramètre) à appeler. Code C #:
la source