Avoid too many Parameters using the Builder Pattern

Today i want to show you another way to use the Builder Pattern. From what i've seen there are tons of examples of the Builder Pattern online. But they do all have one thing in common: They are used to create an Instance of an Object and "replace" its constructor. This way you can create a complex Object in a very clean and readable fashion using a Fluent Interface.

Other than that, there is also a slightly different way you can benefit of the Builder Pattern. Think of a Method with like 10 Parameters. Thats not quite nice, and should not ever happen. Robert C. Martin recommends a maximum amount of 3 Parameters in his book: Clean Code. You should rather use some kind of a Config Object holding those 10 parameters, to clean it up. But lets say, you have a BookService Class with a find(..) method, that allows to search by 10 different parameters, and every single one of them is optional. You could use the Builder Pattern with some kind of Query Object to solve the problem.

As we should always program against Interfaces to keep things modular, ill start with the Interface:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public interface BookService {

public Book create(Book book);

public void delete(String isbn);

public Query query();

public interface Query {

public Query isName(String name);

public Query hasAuthor(String author);

public Query isCheaperThan(Float price);

public Query isIsbn(String isbn);

public Query hasMorePagesThan(Long pages);

public Query isAvailable();

public List<Book> find();

}
}

So the Interface looks pretty neat. It allows you to write something like:

bookService.query().hasAuthor("Bob").isCheaperThan(20.9).isAvailable().find();

While the Query could be considered as an implementation of the Query Object pattern it is not tied to any Database related stuff, which is very important. You dont want to be tied to Criteria Queries or SQL in this Layer. It should be independent of any implementation (e.g. Database or Filesystem).

To clear things up, i want to show you an implementation example using Spring-Data. I am a big fan of Spring-Data, since it allows you to create full featured DAOs with little to no Programming effort for many different Persistence Providers. If you havent used it yet, you should definitely give it a try.

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class BookServiceImpl implements BookService {

@Autowired
private BookRepository bookRepository;

// ...

@Override
public Query query() {
return new QueryImpl(this.bookRepository);
}

public class QueryImpl implements Query{

private BookRepository bookRepository;
private Specifications<Book> filters = null;

public QueryImpl(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}

private void addFilter(Specification<FlowMessage> filter) {
filters = (filters == null) ? where(filter) : filters.and(filter);
}

public Query isName(String name) {
if (name == null) return this;
addFilter(BookSpecifications.isName(name));
return this;
}

public Query hasAuthor(String author) {
if (author == null) return this;
addFilter(BookSpecifications.hasAuthor(author));
return this;
}

// other criterias ...

public List<Book> find() {
return this.bookRepository.findAll(filters);
}
}
}

As you can see i am just joining filter Criterias together, and thats it. The implementation is really simple but also expandable. These filter Criterias are supported by my BookRepository (=DAO). I just have to define them in a helper class first. Check out this and that for a deeper understanding how to combine spring-data with criteria queries.




Comments