多重组合的设计模式

3
如果我需要根据不同的参数存在与否进行不同的数据库查询,有哪种设计模式可以避免使用太多if-else和不同的组合? 假设我有参数a、b、c(数量可能会增加),我正在使用仓储库,因此我需要进行类似这样的调用
public Foo getFoo(String a, String b, String c){
   Foo foo;
   if(a!=null && !a.isEmpty() && b!=null && !b.isEmpty() && c!=null && !c.isEmpty())
      foo = repository.findByAAndBAndC(a,b,c);
   if((a==null || a.isEmpty()) && b!=null && !b.isEmpty() && c!=null && !c.isEmpty())
      foo = repository.findByBAndC(b,c);
   if((a!=null && !a.isEmpty()) && (b==null || b.isEmpty()) && c!=null && !c.isEmpty())
      foo = repository.findByAAndC(a,c);
   if((a==null || a.isEmpty()) && (b==null || b.isEmpty()) && !b.isEmpty() && c!=null && !c.isEmpty())
      foo = repository.findByC(c);
   if((a==null || a.isEmpty()) && (b==null || b.isEmpty()) && !b.isEmpty() && (b==null || b.isEmpty()))
      foo = repository.findOne();
   etc.
   .
   .
   .
   return foo;
}

如何更好地结构化呢?

一个好的开始是使用Apache StringUtils.isEmpty。 - rghome
1
所需的模式是动态查询构建。您最好重新考虑存储库上众多的“findByX”方法,而是使用规范或标准模式,这通常是ORM(如Hibernate或SpringData)提供的。 - StuartLC
1
在我看来,颗粒化的仓库设计才是真正的问题。 - StuartLC
3个回答

8
起初,我建议您使用规范设计模式:
这是一种特殊的软件设计模式,通过使用布尔逻辑将业务规则链接在一起,可以重新组合业务规则。该模式经常在领域驱动设计的上下文中使用。
但是,实际代码并不完全适用于该模式,因为根据情况,您没有调用存储库的相同方法。
因此,我认为您有两种方式:
1)重构您的存储库,提供一个接受规范参数并能够处理不同情况的单个公共方法。
如果您使用Spring,则可以查看JpaSpecificationExecutor接口,该接口提供诸如以下方法:
List<T> findAll(Specification<T> spec)

即使您不使用Spring,我认为这些示例可能会对您有所帮助。
2) 如果您无法重构存储库,则应寻找另一种方法,并提供关于可以传递哪些存储库方法/参数的抽象级别。
实际上,根据输入参数,您调用不同的方法并使用不同的参数,但在任何情况下,您都将相同类型的对象返回给方法的客户端:Foo。因此,为避免条件语句,多态是要遵循的方式。
最终处理每个情况都是不同的策略。因此,您可以拥有一个策略接口,并确定要使用哪个策略来将Foo返回给客户端。
此外,如评论中所建议的:a!= null &&!a.isEmpty()多次重复不是好的代码习惯。它会产生很多重复,并且使代码难以阅读。最好使用像Apache common这样的库或甚至自定义方法来应用此处理。
public class FooService {

    private List<FindFooStrategy> strategies = new ArrayList<>();

    public  FooService(){
      strategies.add(new FindFooByAAndBAndCStrategy());
      strategies.add(new FindFooByBAndCStrategy());
      strategies.add(new FindFooByAAndCStrategy());
      strategies.add(new FindFooByCStrategy());
    }

    public Foo getFoo(String a, String b, String c){

       for (FindFooStrategy strategy : strategies){
            if (strategy.isApplicable(a, b, c)) {
               return strategy.getFoo(a, b, c);
            }
       }
     }
}

其中FindFooStrategy的定义如下:

public interface FindFooStrategy{
  boolean isApplicable(String a, String b, String c);
  Foo getFoo(String a, String b, String c);
}

每个子类定义自己的规则。例如:

public class FindFooByAAndBAndCStrategy implements FindFooStrategy{
  public boolean isApplicable(String a, String b, String c){
      return StringUtils.isNotEmpty(a) && StringUtils.isNotEmpty(b) &&
             StringUtils.isNotEmpty(c);
  }
  public Foo getFoo(String a, String b, String c){
      return repository.findByAAndBAndC(a,b,c);
  } 
}

可能是最面向对象的方式 - Lino

0

这不是一个完整的答案。我将提供几个建议来解决手头的问题。

处理 null 值

为了避免检查一个值是否为 null,我建议您使用一个容器类来存储字符串查询参数,并使用某种方法(例如 getValue())返回参数值,例如,如果值存在,则返回 parameter='value';如果值为 null,则返回一些默认字符串值,例如 parameter like '%'。这种方法遵循所谓的“Null 设计模式”。

动态构建查询条件

这样做之后,你就不再需要关心传递的参数值是什么,只需按迭代的方式构建条件即可,例如:

for parameter in parameters:
    condition = "AND" + parameter.getValue()

也许您可以将此与一种通用的查询方法相结合,该方法接受任意长度的条件,例如:
repository.findBy(condition)

我不是100%确定,因为我是凭记忆输入这个答案的,但我认为这种方法可行并且应该能够解决您在帖子中提到的问题。让我知道您的想法。


0
你可以使用一个定义了位图常量的枚举,并带有valueOf方法:
public enum Combinations{
    A_AND_B_AND_C (0b111),
    B_AND_C       (0b110),
    A_AND_C       (0b101),
    C             (0b100),
    A_AND_B       (0b011),
    B             (0b010),
    A             (0b001),
    NONE          (0b000),
    ;

    private final int bitmap;

    Combinations(int bitmap){
        this.bitmap = bitmap;
    }

    public static Combinations valueOf(String... args){
        final StringBuilder builder = new StringBuilder();
        for(int i = args.length - 1; i >= 0; i--){
            final String arg = args[i];
            builder.append(arg != null && !arg.isEmpty() ? '1' : '0');
        }

        final int bitmap = Integer.parseInt(builder.toString(), 2);

        final Combinations[] values = values();
        for(int i = values.length -1; i >= 0; i--){
            if(values[i].bitmap == bitmap){
                return values[i];
            }
        }

        throw new NoSuchElementException();
    }
}

还有另一个类,其中包含了一个 switch case 语句:

public class SomeClass {

    public Foo getFoo(String a, String b, String c){
         switch(Combinations.valueOf(a, b, c)){
             case A_AND_B_AND_C:
                 return repository.findByAAndBAndC(a, b, c);

             case B_AND_C:
                  return repository.findByBAndC(b, c);

             /* all other cases */

             case NONE:
                  return repository.findOne();

             default:
                  // type unknown
                  throw new UnsupportedOperationException();
         }
    }
}

一开始可能需要花费很多功夫。但是完成后你会感到很高兴的。通过使用位图,您可以有许多组合。 valueOf 方法负责找出实际应该采用哪个组合。但之后应该发生什么无法以通用方式完成。因此,当添加另一个参数 d 时,您将获得更多必须添加到 enum 的组合。

总的来说,对于少量参数来说,这种解决方案过于复杂了。它仍然相当容易理解,因为逻辑被分成许多小部分。但是最终还是无法避免大型开关语句的出现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接