【问题标题】:Replacing if else statement with pattern用模式替换 if else 语句
【发布时间】:2015-03-18 21:39:47
【问题描述】:

我有一个 if else 语句,它可能会在不久的将来增长。

    public void decide(String someCondition){

        if(someCondition.equals("conditionOne")){
            //
            someMethod("someParameter");

        }else if(someCondition.equals("conditionTwo")){

           //
           someMethod("anotherParameter");

        }
        .
        .
        else{

            someMethod("elseParameter");

        }
}

既然这看起来已经很乱了,我认为如果我可以在这里应用任何设计模式会更好。我研究了策略模式,但我不确定这是否会减少 if else 条件。有什么建议吗?

【问题讨论】:

  • 我会选择一些简单的决策表
  • 我不明白你的符号:'conditionOne' 是字符串吗?
  • 策略适用于可替换算法。在您的情况下,请查看命令模式。如果您认为值得付出努力。
  • @FabioF。它是字符串。我更新了帖子。
  • Whittoit 进一步的信息是不可能简单的 if else。你必须解释条件是什么。如果是命令 ID,消息类型 ID,那么就是命令模式。切换可以更快,但这一切都取决于情况

标签: java oop design-patterns


【解决方案1】:

这是 Refactoring to Patterns 一书中的经典 Replace Condition dispatcher with Command

基本上,您为旧 if/else 组中的每个代码块创建一个 Command 对象,然后为这些命令创建一个映射,其中键是您的条件字符串

interface Handler{
    void handle( myObject o);
}


 Map<String, Handler> commandMap = new HashMap<>();
 //feel free to factor these out to their own class or
 //if using Java 8 use the new Lambda syntax
 commandMap.put("conditionOne", new Handler(){
         void handle(MyObject o){
                //get desired parameters from MyObject and do stuff
          }
 });
 ...

然后代替您的 if/else 代码:

 commandMap.get(someCondition).handle(this);

现在,如果您以后需要添加新命令,只需添加到哈希中即可。

如果要处理默认情况,可以使用Null Object 模式来处理条件不在 Map 中的情况。

 Handler defaultHandler = ...

if(commandMap.containsKey(someCondition)){
    commandMap.get(someCondition).handle(this);
}else{
    defaultHandler.handle(this);
}

【讨论】:

  • 我会恭敬地不同意使用命令,除非您需要执行/撤消/重做或将替代项存储为对象。 Command 的意图(来自 GoF 参考):“将请求封装为对象,从而让您可以使用不同的请求参数化客户端、排队或记录请求,并支持可撤消的操作。”
  • @Fuhrmanator 这怎么不是“将请求封装为对象,让您参数化具有不同请求的客户端”?您应该阅读模式重构一书,其中该技术有一整章专门介绍
  • 封装“大量代码”以便(命令)对象成为客户端的参数是有意义的。但是 OPs 问题是每个 if 中的单个方法调用,它仅由一个参数改变。他的评论:“方法总是一样的。论据不同。”
  • 我只是喜欢它,因为它看起来让代码更容易测试。
  • 教程看了很多。这个解释比任何一个都好!
【解决方案2】:

我想你一定已经考虑过了,但是如果你使用的是JDK 7 或更高版本,你可以打开字符串。这样一来,您的代码看起来就比一堆 if-else 语句更简洁。

【讨论】:

    【解决方案3】:

    Martin Fowler 的一般建议是 Replace Conditional with Polymorphism.

    就设计模式而言,这通常是策略模式 Replace Conditional Logic with Strategy.

    如果你有一个有限的有限集条件,我建议使用enum来实现策略模式(在枚举中提供一个抽象方法并覆盖它对于每个常数)。

    public enum SomeCondition{
       CONDITION_ONE{
    
           public void someMethod(MyClass myClass){
                  //...
           }
       },
    
       CONDITION_TWO{
    
           public void someMethod(MyClass myClass){
           }
    
       }
    
       public abstract void someMethod(MyClass myClass);
    
    }
    
    public class MyClass{
    //...
        public void decide(SomeCondition someCondition){
            someCondition.someMethod(this);
        }
    
    }
    

    如果它真的只是一个你想选择的参数,那么你可以像这样定义枚举:

    public enum SomeCondition{
       CONDITION_ONE("parameterOne"),
    
       CONDITION_TWO("parameterTwo");
    
       private final String parameter;
    
       private SomeCondition(String parameter){
           this.parameter = parameter;
       }
    
       public String getParameter(){
           return parameter;
       }
    
    }
    
    
    public class MyClass{
    //...
        public void decide(SomeCondition someCondition){
            someMethod(someCondition.getParameter());
        }
    
    }
    

    【讨论】:

      【解决方案4】:

      解决当前问题的另一种方法是使用工厂模式。这提供了提取工厂方法的功能,该方法返回给定类型的对象并根据具体的对象行为执行操作。

      public interface Operation {
      
      String process(String a, String b);
      }
      

      该方法接受两个字符串作为输入并返回结果。

      public class Concatenation implements Operation {
      
        @Override
        public String process(String a, String b) {
          return a.concat(b);
        }
      }
      
      public class Join implements Operation {
      
        @Override
        public String process(String a, String b) {
          return String.join(", ", a, b);
        }
      }
      

      然后我们应该定义一个工厂类,它根据给定的运算符返回 Operation 的实例:

      public class OperatorFactory {
      
        static Map<String, Operation> operationMap = new HashMap<>();
      
        static {
          operationMap.put("concatenation", new Concatenation());
          operationMap.put("join", new Join());
          // more operators
        }
      
        public static Optional<Operation> getOperation(String operator) {
          return Optional.ofNullable(operationMap.get(operator));
        }
      }
      

      现在我们可以使用它了:

      public class SomeServiceClass {
      
            public String processUsingFactory(String a, String b, String operationName) {
              Operation operation = OperatorFactory
                  .getOperation(operationName)
                  .orElseThrow(() -> new IllegalArgumentException("Invalid Operation"));
      
              return operation.process(a, b);
            }
          }
      

      【讨论】:

        【解决方案5】:

        假设我们有这样的代码(和你的一样):

            public void decide(String someCondition) {
                if(someCondition.equals("conditionOne")) {
                    someMethod("someParameter");
                }
                else if(someCondition.equals("conditionTwo")) {
                    someMethod("anotherParameter");
                }
                else {
                    someMethod("elseParameter");
                }
            }
        

        假设您不想重构应用程序的其他部分并且您不想更改方法签名,那么可以通过多种方式对其进行重构:

        警告 - 您应该使用上述模式的通用版本。
        我展示了非通用的,因为它更容易阅读。

        策略 + 工厂方法
        我们可以使用策略和工厂方法模式。我们还利用了多态性。

          private final StrategyConditionFactory strategyConditionFactory = new StrategyConditionFactory();
        
            public void decide(String someCondition) {
                Strategy strategy = strategyConditionFactory.getStrategy(someCondition)
                        .orElseThrow(() -> new IllegalArgumentException("Wrong condition"));
                strategy.apply();
            }
        

        最好将其设计为工厂中包含 else 条件,开发人员故意调用它。在这种情况下,当条件不满足时,我们会抛出异常。或者,我们可以完全按照所讨论的那样编写它。如果你想这样而不是.orElseThrow(() -&gt; new IllegalArgumentException("Wrong condition"));.orElse(new ElseStrategy());

        StrategyConditionFactory(工厂方法):

            public class StrategyConditionFactory {
                private Map<String, Strategy> conditions = new HashMap<>();
            
                public StrategyConditionFactory() {
                    conditions.put("conditionOne", new ConditionOneStrategy());
                    conditions.put("conditionTwo", new ConditionTwoStrategy());
                    //It is better to call else condition on purpose than to have it in the conditional method
                    conditions.put("conditionElse", new ElseStrategy());
                    //...
                }
            
                public Optional<Strategy> getStrategy(String condition) {
                    return Optional.ofNullable(conditions.get(condition));
                }
            }
        

        策略界面:

        public interface Strategy {
            void apply();
        }
        

        实现:

            public class ConditionOneStrategy implements Strategy {
                @Override
                public void apply() {
                    //someMethod("someParameter");
                }
            }  
        
            public class ConditionTwoStrategy implements Strategy {
                @Override
                public void apply() {
                    //someMethod("anotherParameter")
                }
            }
        
            public class ElseStrategy implements Strategy {
                @Override
                public void apply() {
                    //someMethod("elseParameter")
                }
            }
        

        用法(简体):

            public void strategyFactoryApp() {
                //...
                decide("conditionOne");
                decide("conditionTwo");
                decide("conditionElse");
                //...
            }
        

        策略 + 工厂方法 - 这种特殊情况(仅参数更改)
        我们可以利用这样一个事实,在这种情况下,我们总是调用相同的方法,只是参数更改
        我们使用 getParameter() 方法将基本策略接口更改为抽象类,并对这个抽象类进行新的实现。其他代码保持不变。

        public abstract class Strategy {
            public abstract String getParameter();
        
            public void apply() {
                someMethod(getParameter());
            }
        
            private void someMethod(String parameter) {
                //someAction
            }
        }
        
        

        实现:

        public class CondtionOneStrategy extends Strategy {
            @Override
            public String getParameter() {
                return "someParameter";
            }
        }
        
        public class CondtionTwoStrategy extends Strategy {
            @Override
            public String getParameter() {
                return "anotherParameter";
            }
        }
        
        public class ElseStrategy extends Strategy {
            @Override
            public String getParameter() {
                return "elseParameter";
            }
        }
        
        

        枚举 + 枚举有点“工厂”
        我们可以使用 Enum 来实现策略,而不是工厂方法,我们可以使用 enum 中的 valueOf()。

            public void decide(String someCondition) {
                    ConditionEnum conditionEnum = ConditionEnum.valueOf(someCondition);
                    conditionEnum.apply();
                }
        

        条件枚举:

        public enum ConditionEnum {
            CONDITION_ONE {
                @Override
                public void apply() {
                    //someMethod("someParameter");
                }
            },
            CONDITION_TWO {
                @Override
                public void apply() {
                    //someMethod("anotherParameter");
                }
            },
            CONDITION_ELSE {
                @Override
                public void apply() {
                    //someMethod("elseParameter");
                }
            };
            //...more conditions
        
            public abstract void apply();
        }
        

        用法(简体):

            public void enumFactoryApp() {
                //...
                decide("CONDITION_ONE");
                decide("CONDITION_TWO");
                decide("CONDITION_ELSE");
                //...
            }
        

        请注意,当枚举类型没有具有指定名称的常量时,您将获得IllegalArgumentException

        命令 + 工厂
        策略和命令之间的区别在于命令也保存状态,所以如果你有例如 compute(int a, int b, String someCondition) 并且你想用策略重构它,包括它的签名更改,你可以将它减少到 compute(int a, int b, ComputeStrategy computeStrategy) 使用命令,您可以将其简化为一个参数计算(ComputeCommand computeCommand)。在这种情况下,我们还利用了类似于策略模式案例的多态性。

            CommandConditionFactory commandConditionFactory = new CommandConditionFactory();
        
            public void decide(String someCondition) {
                Command command = commandConditionFactory.getCommand(someCondition)
                        .orElseThrow(() -> new IllegalArgumentException("Wrong condition"));
                command.apply();
            }
        

        最好将其设计为工厂中包含 else 条件,开发人员故意调用它。在这种情况下,当条件不满足时,我们会抛出异常。或者,我们可以完全按照所讨论的那样编写它。如果你想这样而不是.orElseThrow(() -&gt; new IllegalArgumentException("Wrong condition"));.orElse(new ElseCommand());

        CommandConditionFactory(工厂方法):

        public class CommandConditionFactory {
            private Map<String, Command> conditions = new HashMap<>();
        
            public CommandConditionFactory() {
                conditions.put("conditionOne", new ConditionOneCommand("someParameter"));
                conditions.put("conditionTwo", new ConditionTwoCommand("anotherParameter"));
                //It is better to call else condition on purpose than to have it in the conditional method
                conditions.put("conditionElse", new ElseCommand("elseParameter"));
                //...
            }
        
            public Optional<Command> getCommand(String condition) {
                return Optional.ofNullable(conditions.get(condition));
            }
        }
        

        命令界面:

        public interface Command {
            void apply();
        }
        

        实现(有一些冗余,但它显示了在更一般的情况下命令应该如何看待,而不是 someMethod() 我们有三种不同的方法):

        public class ConditionOneCommand implements Command {
            private final String parameter;
        
            public ConditionOneCommand(String parameter) {
                this.parameter = parameter;
            }
        
            @Override
            public void apply() {
                //someMethod(parameter);
            }
        }
        
        public class ConditionTwoCommand implements Command {
            private final String parameter;
        
            public ConditionTwoCommand(String parameter) {
                this.parameter = parameter;
            }
        
            @Override
            public void apply() {
                //someMethod(parameter);
            }
        }
        
        public class ElseCommand implements Command {
            private final String parameter;
        
            public ElseCommand(String parameter) {
                this.parameter = parameter;
            }
        
            @Override
            public void apply() {
                //someMethod(parameter);
            }
        }
        
        

        用法(简体):

            public void commandFactoryApp() {
                //...
                decide("conditionOne");
                decide("conditionTwo");
                decide("conditionElse");
                //...
            }
        

        Command + Factory - 这种特殊情况。
        这实际上不是一个真正的命令模式,只是一个衍生物。它利用了这样一个事实,在这种情况下,我们总是调用同一个方法 someMethod(parameter) 并且只有参数发生变化。
        抽象类:

        public abstract class Command {
            abstract void apply();
        
            protected void someMethod(String parameter) {
                //someAction
            }
        }
        

        实现(所有 3 个条件案例都相同):

        public class CommandImpl extends Command {
            private final String parameter;
        
            public CommandImpl (String parameter) {
                this.parameter = parameter;
            }
        
            @Override
            public void apply(){
                someMethod(parameter);
            }
        }
        

        工厂,请注意只有一个命令实现,只有参数变化:

        public class CommandConditionFactory {
            Map<String, Command> conditions = new HashMap<>();
        
            public CommandConditionFactory() {
                conditions.put("conditionOne", new CommandImpl("someParameter"));
                conditions.put("conditionTwo", new CommandImpl("anotherParameter"));
                //It is better to call else condition on purpose than to have it in the conditional method
                conditions.put("conditionElse", new CommandImpl("elseParameter"));
                //...
            }
        
            public Optional<Command> getCommand(String condition) {
                return Optional.ofNullable(conditions.get(condition));
            }
        }
        

        嵌套 if's
        请注意,即使您嵌套了 if,有时也可以重构它们并使用上述技术之一。 假设我们有以下代码:

            public void decide2(String someCondition, String nestedCondition) {
                if(someCondition.equals("conditionOne")) {
                    if(nestedCondition.equals("nestedConditionOne")){
                        someLogic1();
                    }
                    else if(nestedCondition.equals("nestedConditionTwo")){
                        someLogic2();
                    }
                }
                else if(someCondition.equals("conditionTwo")) {
                    if(nestedCondition.equals("nestedConditionThree")){
                        someLogic3();
                    }
                    else if(nestedCondition.equals("nestedConditionFour")){
                        someLogic4();
                    }
                }
            }
        

        您可以使用数学逻辑规则对其进行重构:

            public void decide2(String someCondition, String nestedCondition) {
                if(someCondition.equals("conditionOne")
                        && nestedCondition.equals("nestedConditionOne")) {
                    someLogic1();
                }
                else if(someCondition.equals("conditionOne")
                        && nestedCondition.equals("nestedConditionTwo")) {
                    someLogic2();
                }
                else if(someCondition.equals("conditionTwo")
                        && nestedCondition.equals("nestedConditionThree")) {
                    someLogic3();
                }
                else if(someCondition.equals("conditionTwo")
                        && nestedCondition.equals("nestedConditionFour")) {
                    someLogic4();
                }
            }
        

        然后你可以使用策略、枚举或命令。您只有一对字符串 而不是单个字符串。

        决策表
        当您嵌套了如前所述无法重构的 if 时,您可以实现自己的决策表或使用一些准备就绪的决策表解决方案。我不会在那里给出实现。

        规则引擎
        当您嵌套了如前所述无法重构的 if 时,您还可以实现自己的简单规则引擎。只有当你有许多嵌套的 if 时才应该使用它,否则它就是形式战胜内容。
        对于非常复杂的业务逻辑,有像 Drools 这样的专业规则引擎。
        我不会在那里给出实现。

        还有一件事
        在您给出的示例中,很可能有人引入了这些 if,但它们完全是多余的。我们可以通过尝试重构确定方法签名以使其接受其他参数并重构调用我们方法的周围代码来检查它。通过这样做,我们摆脱了我们的工厂方法。有一些示例展示了当这些 if 是多余的情况下代码的外观。

        策略
        决定方法:

            public void decide(Strategy strategy) {
                strategy.apply();
            }
        

        用法(简体):

            public void strategyApp() {
                //...
                decide(new ConditionOneStrategy());
                decide(new ConditionTwoStrategy());
                decide(new ElseStrategy());
                //...
            }
        

        枚举
        决定方法:

            public void decide(ConditionEnum conditionEnum) {
                conditionEnum.apply();
            }
        

        用法(简体):

            public void enumApp() {
                //...
                decide(ConditionEnum.CONDITION_ONE);
                decide(ConditionEnum.CONDITION_TWO);
                decide(ConditionEnum.CONDITION_ELSE);
                //...
            }
        

        命令
        决定方法:

            public void decide(Command command) {
                command.apply();
            }
        

        用法(简体):

            public void commandApp() {
                //...
                decide(new ConditionOneCommand("someParameter"));
                decide(new ConditionTwoCommand("anotherParameter"));
                decide(new ElseCommand("elseParameter"));
                //...
            }
        

        事实上这是非常特殊的情况,在某些情况下,例如我们必须使用像 String 这样的简单类型,因为它来自外部系统或条件基于输入的整数,因此我们无法重构代码这么简单。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-11-29
          • 1970-01-01
          • 1970-01-01
          • 2016-08-03
          • 1970-01-01
          • 2019-04-09
          • 1970-01-01
          相关资源
          最近更新 更多