At the beginning, I would propose you the Specification design pattern that :
is a particular software design pattern, whereby business rules can be
recombined by chaining the business rules together using boolean
logic. The pattern is frequently used in the context of domain-driven
design.
but your actual code doesn't suit completely to that as you don't invoke the same method of the repository according to the case.
So I think that you have two ways :
1) Refactoring your repository to provide a single common method accepting a specification parameter and able to handle the different cases.
If you use Spring, you could look at the JpaSpecificationExecutor
interface that provides methods such as :
List<T> findAll(Specification<T> spec)
Even if you don't use Spring, I think that these examples could help you .
2) If you cannot refactor the repository, you should look for another way and provide a abstraction level about which repository methods/parameters may be passed to.
Actually, you invoke a different method with different parameters according to the input parameters but in any case you return the same type of object to the client of the method : Foo
. So to avoid conditional statements, polymorphism is the way to follow.
Each case to handle is finally a different strategy. So you could have a strategy interface and you could determine the strategy to use to return the Foo to the client.
Besides, as suggested in a comment : a!=null && !a.isEmpty()
repeated multiple times is not a good smell. It makes much duplication and also makes the code less readable. It would better to apply this processing by using a library such as Apache common or even a custom method.
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);
}
}
}
}
Where FindFooStrategy
is defined as :
public interface FindFooStrategy{
boolean isApplicable(String a, String b, String c);
Foo getFoo(String a, String b, String c);
}
And where each subclass defines its rules. For example :
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);
}
}