用模式替换if else语句

cos*_*mos 38 java oop design-patterns

我有一个if else声明可能会在不久的将来增长.

    public void decide(String someCondition){

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

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

           //
           someMethod("anotherParameter");

        }
        .
        .
        else{

            someMethod("elseParameter");

        }
}
Run Code Online (Sandbox Code Playgroud)

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

dka*_*zel 38

这是一个经典的Replace Condition调度程序,在Refactoring to Patterns一书中使用了Command.

在此输入图像描述

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

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
          }
 });
 ...
Run Code Online (Sandbox Code Playgroud)

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

 commandMap.get(someCondition).handle(this);
Run Code Online (Sandbox Code Playgroud)

现在,如果您需要稍后添加新命令,则只需添加到哈希.

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

 Handler defaultHandler = ...

if(commandMap.containsKey(someCondition)){
    commandMap.get(someCondition).handle(this);
}else{
    defaultHandler.handle(this);
}
Run Code Online (Sandbox Code Playgroud)

  • 我会恭敬地不同意使用Command,除非您需要执行/撤消/重做或将替代项存储为对象.Command的意图(来自GoF参考):"将请求封装为对象,从而允许您使用不同的请求,队列或日志请求参数化客户端,并支持可撤销的操作." (3认同)
  • 封装“大量代码”是有意义的,以便(Command)对象成为客户端的参数。但是OP的问题是每个if中只有一个方法调用,只能通过一个参数来更改。他的评论是:“方法总是相同的。参数不同。” (2认同)

Puc*_*uce 18

Martin Fowler的一般建议是 将条件替换为多态.

在设计模式方面,这通常是策略模式策略 替换条件逻辑.

如果你有一小组有限的条件,我建议使用枚举来实现策略模式(在枚举中提供一个抽象方法并为每个常量覆盖它).

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);
    }

}
Run Code Online (Sandbox Code Playgroud)

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

public enum SomeCondition{
   CONDITION_ONE("parameterOne"),

   CONDITION_TWO("parameterTwo");

   private SomeCondition(String parameter){
       this.parameter = parameter;
   }

   public String getParameter(){
       return parameter;
   }

}


public class MyClass{
//...
    public void decide(SomeCondition someCondition){
        someMethod(someCondition.getParameter());
    }

}
Run Code Online (Sandbox Code Playgroud)


luk*_*uke 14

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

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

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

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

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

  private final StrategyConditionFactory strategyConditionFactory = new StrategyConditionFactory();

    public void decide(String someCondition) {
        Strategy strategy = strategyConditionFactory.getStrategy(someCondition)
                .orElseThrow(() -> new IllegalArgumentException("Wrong condition"));
        strategy.apply();
    }
Run Code Online (Sandbox Code Playgroud)

最好以工厂中包含其他条件的方式设计它,并且开发人员故意调用它。在这种情况下,当条件不满足时我们会抛出异常。或者,我们可以完全按照所讨论的方式编写它。如果你想要而不是.orElseThrow(() -> 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));
        }
    }
Run Code Online (Sandbox Code Playgroud)

策略界面:

public interface Strategy {
    void apply();
}
Run Code Online (Sandbox Code Playgroud)

实现:

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

用法(简化):

    public void strategyFactoryApp() {
        //...
        decide("conditionOne");
        decide("conditionTwo");
        decide("conditionElse");
        //...
    }
Run Code Online (Sandbox Code Playgroud)

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

public abstract class Strategy {
    public abstract String getParameter();

    public void apply() {
        someMethod(getParameter());
    }

    private void someMethod(String parameter) {
        //someAction
    }
}

Run Code Online (Sandbox Code Playgroud)

实现:

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

Run Code Online (Sandbox Code Playgroud)

Enum + enum 有点像“工厂”
我们可以使用 Enum 来实现策略,我们可以使用 enum 中的 valueOf() 代替工厂方法。

    public void decide(String someCondition) {
            ConditionEnum conditionEnum = ConditionEnum.valueOf(someCondition);
            conditionEnum.apply();
        }
Run Code Online (Sandbox Code Playgroud)

条件枚举:

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();
}
Run Code Online (Sandbox Code Playgroud)

用法(简化):

    public void enumFactoryApp() {
        //...
        decide("CONDITION_ONE");
        decide("CONDITION_TWO");
        decide("CONDITION_ELSE");
        //...
    }
Run Code Online (Sandbox Code Playgroud)

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

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

    CommandConditionFactory commandConditionFactory = new CommandConditionFactory();

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

最好以工厂中包含其他条件的方式设计它,并且开发人员故意调用它。在这种情况下,当条件不满足时我们会抛出异常。或者,我们可以完全按照所讨论的方式编写它。如果你想要而不是.orElseThrow(() -> 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));
    }
}
Run Code Online (Sandbox Code Playgroud)

命令界面:

public interface Command {
    void apply();
}
Run Code Online (Sandbox Code Playgroud)

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

public class ConditionOneCommand implements Command {
    private final String parameter;

    public ConditionOneCommand(String parameter) {
        this.parameter = parameter;
    }

    @Override
    public void apply() {
        //someMethod(parameter);
    }
}
Run Code Online (Sandbox Code Playgroud)
public class ConditionTwoCommand implements Command {
    private final String parameter;

    public ConditionTwoCommand(String parameter) {
        this.parameter = parameter;
    }

    @Override
    public void apply() {
        //someMethod(parameter);
    }
}
Run Code Online (Sandbox Code Playgroud)
public class ElseCommand implements Command {
    private final String parameter;

    public ElseCommand(String parameter) {
        this.parameter = parameter;
    }

    @Override
    public void apply() {
        //someMethod(parameter);
    }
}

Run Code Online (Sandbox Code Playgroud)

用法(简化):

    public void commandFactoryApp() {
        //...
        decide("conditionOne");
        decide("conditionTwo");
        decide("conditionElse");
        //...
    }
Run Code Online (Sandbox Code Playgroud)

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

public abstract class Command {
    abstract void apply();

    protected void someMethod(String parameter) {
        //someAction
    }
}
Run Code Online (Sandbox Code Playgroud)

实现(对于所有 3 个条件情况都相同):

public class CommandImpl extends Command {
    private final String parameter;

    public CommandImpl (String parameter) {
        this.parameter = parameter;
    }

    @Override
    public void apply(){
        someMethod(parameter);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

嵌套 if
请注意,即使您有时嵌套 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();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

您可以使用数学逻辑规则重构它:

    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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

决策表
当您嵌套了无法重构的 ifs 时,您可以实现自己的决策表或使用一些现成的决策表解决方案。我不会在那里给出实现。

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

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

策略
决定方法:

    public void decide(Strategy strategy) {
        strategy.apply();
    }
Run Code Online (Sandbox Code Playgroud)

用法(简化):

    public void strategyApp() {
        //...
        decide(new ConditionOneStrategy());
        decide(new ConditionTwoStrategy());
        decide(new ElseStrategy());
        //...
    }
Run Code Online (Sandbox Code Playgroud)

枚举
决定方法:

    public void decide(ConditionEnum conditionEnum) {
        conditionEnum.apply();
    }
Run Code Online (Sandbox Code Playgroud)

用法(简化):

    public void enumApp() {
        //...
        decide(ConditionEnum.CONDITION_ONE);
        decide(ConditionEnum.CONDITION_TWO);
        decide(ConditionEnum.CONDITION_ELSE);
        //...
    }
Run Code Online (Sandbox Code Playgroud)

命令
决定方法:

    public void decide(Command command) {
        command.apply();
    }
Run Code Online (Sandbox Code Playgroud)

用法(简化):

    public void commandApp() {
        //...
        decide(new ConditionOneCommand("someParameter"));
        decide(new ConditionTwoCommand("anotherParameter"));
        decide(new ElseCommand("elseParameter"));
        //...
    }
Run Code Online (Sandbox Code Playgroud)

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