J'essaie d'écrire un servlet qui effectue une tâche en fonction de la valeur "action" transmise en entrée.
Voici l'exemple dont
public class SampleClass extends HttpServlet {
public static void action1() throws Exception{
//Do some actions
}
public static void action2() throws Exception{
//Do some actions
}
//And goes on till action9
public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
String action = req.getParameter("action");
/**
* I find it difficult in the following ways
* 1. Too lengthy - was not comfortable to read
* 2. Makes me fear that action1 would run quicker as it was in the top
* and action9 would run with a bit delay - as it would cross check with all the above if & else if conditions
*/
if("action1".equals(action)) {
//do some 10 lines of action
} else if("action2".equals(action)) {
//do some action
} else if("action3".equals(action)) {
//do some action
} else if("action4".equals(action)) {
//do some action
} else if("action5".equals(action)) {
//do some action
} else if("action6".equals(action)) {
//do some action
} else if("action7".equals(action)) {
//do some action
} else if("action8".equals(action)) {
//do some action
} else if("action9".equals(action)) {
//do some action
}
/**
* So, the next approach i tried it with switch
* 1. Added each action as method and called those methods from the swith case statements
*/
switch(action) {
case "action1": action1();
break;
case "action2": action2();
break;
case "action3": action3();
break;
case "action4": action4();
break;
case "action5": action5();
break;
case "action6": action6();
break;
case "action7": action7();
break;
case "action8": action8();
break;
case "action9": action9();
break;
default:
break;
}
/**
* Still was not comfortable since i am doing un-necessary checks in one way or the other
* So tried with [reflection][1] by invoking the action methods
*/
Map<String, Method> methodMap = new HashMap<String, Method>();
methodMap.put("action1", SampleClass.class.getMethod("action1"));
methodMap.put("action2", SampleClass.class.getMethod("action2"));
methodMap.get(action).invoke(null);
/**
* But i am afraid of the following things while using reflection
* 1. One is Security (Could any variable or methods despite its access specifier) - is reflection advised to use here?
* 2. Reflection takes too much time than simple if else
*/
}
}
Tout ce dont j'ai besoin est d'échapper à trop de contrôles if/else-if dans mon code pour une meilleure lisibilité et une meilleure maintenance du code. Donc essayé pour d'autres alternatives comme
1. boîtier de commutateur - toujours il fait trop de vérifications avant de faire mon action
2. réflexion
i] une chose principale est la sécurité - qui me permet d'accéder même aux variables et méthodes de la classe malgré son spécificateur d'accès - je ne suis pas sûr que je pourrais l'utiliser dans mon code
ii] et l'autre est que cela prend plus de temps que les simples vérifications if/else-if
Existe-t-il une meilleure approche ou une meilleure conception que quelqu'un pourrait suggérer pour organiser le code ci-dessus d'une meilleure manière?
[~ # ~] modifié [~ # ~]
J'ai ajouté le réponse pour l'extrait ci-dessus considérant la réponse ci-dessous .
Mais encore, les classes suivantes "ExecutorA" et "ExecutorB" ne font que quelques lignes de code. Est-ce une bonne pratique de les ajouter en tant que classe plutôt que de les ajouter en tant que méthode? Veuillez conseiller à cet égard.
Basé sur la réponse précédente, Java permet aux énumérations d'avoir des propriétés afin que vous puissiez définir un modèle de stratégie, quelque chose comme
public enum Action {
A ( () -> { //Lambda Sintax
// Do A
} ),
B ( () -> executeB() ), // Lambda with static method
C (new ExecutorC()) //External Class
public Action(Executor e)
this.executor = e;
}
//OPTIONAL DELEGATED METHOD
public foo execute() {
return executor.execute();
}
// Action Static Method
private static foo executeB(){
// Do B
}
}
Alors votre Executor
(Stratégie) serait
public interface Executor {
foo execute();
}
public class ExecutorC implements Executor {
public foo execute(){
// Do C
}
}
Et tous vos if/else dans votre méthode doPost
deviennent quelque chose comme
public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
String action = req.getParameter("action");
Action.valueOf(action).execute();
}
De cette façon, vous pouvez même utiliser des lambdas pour les exécuteurs dans les énumérations.
Au lieu d'utiliser la réflexion, utilisez une interface dédiée.
c'est-à-dire au lieu de:
/**
* Still was not comfortable since i am doing un-necessary checks in one way or the other
* So tried with [reflection][1] by invoking the action methods
*/
Map<String, Method> methodMap = new HashMap<String, Method>();
methodMap.put("action1", SampleClass.class.getMethod("action1"));
methodMap.put("action2", SampleClass.class.getMethod("action2"));
methodMap.get(action).invoke(null);
Utilisation
public interface ProcessAction{
public void process(...);
}
Implémente chacun d'eux pour chaque action puis:
// as attribute
Map<String, ProcessAction> methodMap = new HashMap<String, ProcessAction>();
// now you can add to the map you can either hardcode them in an init function
methodMap.put("action1",action1Process);
// but if you want some more flexibility you should isolate the map in a class dedicated :
// let's say ActionMapper and add them on init :
public class Action1Manager{
private static class ProcessAction1 implements ProcessAction{...}
public Action1Manager(ActionMapper mapper){
mapper.addNewAction("action1", new ProcessAction1());
}
}
Bien sûr, cette solution n'est pas la plus légère, donc vous n'aurez peut-être pas besoin d'aller jusqu'à cette longueur.
Utilisez le Command Pattern , cela nécessitera une interface de commande quelque chose comme ceci:
interface CommandInterface {
CommandInterface execute();
}
Si les Actions
sont légers et peu coûteux à construire, utilisez une méthode d'usine. Chargez les noms de classe à partir d'un fichier de propriétés qui mappe actionName=className
et utilisez une méthode d'usine simple pour construire les actions à exécuter.
public Invoker execute(final String targetActionName) {
final String className = this.properties.getProperty(targetAction);
final AbstractCommand targetAction = (AbstractCommand) Class.forName(className).newInstance();
targetAction.execute();
return this;
}
Si les actions sont coûteuses à construire, utilisez un pool, tel que HashMap ; cependant, dans la plupart des cas, je dirais que cela pourrait être évité en vertu du principe de responsabilité unique déléguer l'élément coûteux à certains pré- construit un pool de ressources communes plutôt que les commandes elles-mêmes.
public class CommandMap extends HashMap<String, AbstractAction> { ... }
Ceux-ci peuvent ensuite être exécutés avec
public Invoker execute(final String targetActionName) {
commandMap.get(targetActionName).execute();
return this;
}
Il s'agit d'une approche très robuste et découplée qui applique SRP, LSP et ISP des principes SOLID . Les nouvelles commandes ne modifient pas le code du mappeur de commandes. Les commandes sont simples à implémenter. Ils peuvent être simplement ajoutés au projet et au fichier de propriétés. Les commandes doivent être rentrées et cela le rend très performant.
Le modèle de méthode d'usine est ce que je recherche si vous recherchez une conception évolutive et moins maintenable.
Le modèle de méthode d'usine définit une interface pour créer un objet, mais laisse la sous-classe décider de la classe à instancier. La méthode d'usine permet à une classe de reporter l'instanciation à la sous-classe.
abstract class action {abstract doStuff(action)}
action1, action2 ........ actionN implémentation concrète avec la méthode doStuff implémentant la chose à faire.
Il suffit d'appeler
action.doStuff(actionN)
Donc, à l'avenir, si plus d'actions sont introduites, il vous suffit d'ajouter une classe concrète.
Vous pouvez utiliser l'objet basé sur l'énumération pour réduire le besoin de coder en dur les valeurs de chaîne. Cela vous fera gagner du temps et rendra le code beaucoup plus agréable à lire et à étendre à l'avenir.
public static enum actionTypes {
action1, action2, action3....
}
public void doPost {
...
switch (actionTypes.valueOf(action)) {
case action1: do-action1(); break;
case action2: do-action2(); break;
case action3: do-action3(); break;
}
}
En référence à @J. Pichardo réponse j'écris la modification de l'extrait ci-dessus comme suit
public class SampleClass extends HttpServlet {
public enum Action {
A (new ExecutorA()),
B (new ExecutorB())
Executor executor;
public Action(Executor e)
this.executor = e;
}
//The delegate method
public void execute() {
return executor.execute();
}
}
public foo Executor {
foo execute();
}
public class ExecutorA implements Executor{
public void execute() {
//Do some action
}
}
public class ExecutorB implements Executor{
public void execute() {
//Do some action
}
}
public void doPost(HttpServletRequest req, HttpServletResponse res)throws ServletException, IOException {
String action = req.getParameter("action");
Action.valueOf(action).execute();
}
}