避免ifs使用模式

时间:2017-06-28 10:44:05

标签: java oop design-patterns

我知道你可以通过使用带有策略或命令模式的多态来删除if块。但是,让我们说我的代码看起来像这样:

ArgumentObject arg = getArg();

if (arg.getId() == "id2_1" || arg.getId() == "id3_1" || arg.getId() == "id4_1") {
    //do something
}
if (arg.getId() == "id2_1") {
    //do something
}
if (arg.getId() = "id2_2" || arg.getId() = "id3_1") {
    //do something
}
if (arg.getId().startsWith("id1") || arg.getId().startsWith("id4")) {
    //do something
}

基本上,代码根据产品的ID执行一些字段填充操作。它使用startsWith()检查产品是否来自某些产品组,然后执行填充,检查产品是否具有特定ID,然后适当地填充字段等。

我不确定如何在此处使用策略模式?如果我创建一个策略来处理第一个if语句,那么我该如何处理第二个语句?问题是ifs不是互斥的。我应该将相关的ifs移动到Strategy类吗?任何可以帮我重构的模式都比较容易测试吗?

3 个答案:

答案 0 :(得分:1)

您的代码显示的问题更基本:它违反了 Tell!不要问!原则。

您的getId()对象应该具有一个在此时调用的公共接口方法,而不是方法arg。要实现的工作可以在实例化时注入arg个对象,也可以作为参数传递给方法,arg对象的具体实现决定调用哪个参数。

答案 1 :(得分:0)

您的问题缺乏这些ifs的业务逻辑。 但我可以做出一些假设:

  

基本上,代码根据产品的id执行一些字段填充操作。它使用startsWith()检查产品是否来自某些产品组,然后执行填充,检查产品是否具有特定ID,然后适当地填充字段等。

如果块是特定的业务逻辑并且彼此独立,我假设所有你。 您可以尝试使用Decorator Pattern。正如维基所说:

装饰器模式是一种设计模式,允许将行为静态或动态地添加到单个对象,而不会影响同一类中其他对象的行为

您的案例可以视为:

public Product{

    private String id;
    //other fields
}


   ProductDecoratorInterface{

       Product populateItem(Product product);

    }

ProductXYZDecorator{

   @Override
   public Product populateItem(Product product){
   // check the id for 1,2,3
   //do the field population

}

}

    ProductABCDecorator{

    @Override
    public Product populateItem(Product product){
    // check the id for 1,3
    //do the field population

    }

    }

您的主要课程如下:

public Mainclass{

List<ProductDecoratorInterface> decorators;


public void someFn(Product product){
   decorators.stream.forEach(i -> i.populateItem(product));
}

}

}

...

  

任何可以帮我重构的模式,以便更容易测试?

在这里,您需要仅测试各个装饰器。

您需要维护所有装饰器的列表并将对象传递给所有装饰器。 这不是模式的正确使用,但有助于解决您的用例(测试复杂性)。

答案 2 :(得分:0)

您可能无法完全摆脱基于if的代码,但您可以将其整理成更合适的类。

if域逻辑,如果在域内有意义,那么您可以编写一个对此分类负责的对象:

 interface ArgumentClassifier {
      Classification classify(Argument arg);
 }

Classification将是enum

此实现将包含您的if,封装该域逻辑。

  • 您可以单独对ArgumentClassifier进行单元测试。
  • 如果您的ArgumentClassifier变得庞大而复杂,您可能可以使用复合模式将其分解。
  • 如果您稍后找到了进行分类的不同方法(例如规则引擎,数据库查找等),您可以编写ArgumentClassifier的新实现。

现在您可以使用策略模式:

  Map<Classification, Handler> strategies = ...;
  ArgumentClassifier classifier = new SimpleArgumentClassifier();

  private Foo handleArg(Argument arg) {
      return strategies
          .get(classifier.classify(arg))
          .handle(arg);  
  }

这一切是否值得,取决于您的具体需求。

如果:

  • 您正在工作的课程已经是该领域逻辑的自然之家
  • 你正在上课的课程具有良好的凝聚力

...然后将if逻辑保留在原来的位置可能更明智。

另一种分类方法是从关于被分类对象的谓词构建二进制数或字符串。例如:

 int classification(Person p) {
     return 0
         | ( p.getGender() == Gender.MALE ? 1 : 0 )
         | ( p.isEmployed()               ? 2 : 0 )
         | ( p.age >= 18                  ? 4 : 0 );
 } 

对于17岁的失业男性,0b001,对于17岁的就业女性,0b010分类为if/else,依此类推。

您可以直接将它们用作策略图的键(它可以是多对一的)。或者你可以进行进一步的按位操作,将它们缩小到较小的值。

这里有很多范围,但要注意不要忽视简洁和清晰。有时Date链可以完成工作,清晰有效。