Depending on your appetite for complexity, you can do the following:
- Try to group related columns instances into objects like
HeaderColumns
, BodyColumns
, etc.
- Implement the visitor pattern as described here.
Here is a possible implementation of the pattern following the suggestions above:
public class Main {
interface Visitor {
void visit(ReportHeader header);
void visit(ReportBody body);
}
interface Visitable {
void accept(Visitor visitor);
}
static class ReportHeader implements Visitable {
private final List<String> columns = new ArrayList<>();
private final List<String> extras = new ArrayList<>();
@Override
public void accept(Visitor visitor) { visitor.visit(this); }
public List<String> getColumns() { return columns; }
public List<String> getExtras() { return extras; }
}
static class ReportBody implements Visitable {
private final List<String> columns = new ArrayList<>();
@Override
public void accept(Visitor visitor) { visitor.visit(this); }
public List<String> getColumns() { return columns; }
}
static class ExcelReportVisitor implements Visitor {
private final List<String> columns = new ArrayList<>();
@Override
public void visit(ReportHeader header) {
columns.addAll(header.getColumns());
columns.addAll(header.getExtras());
}
@Override
public void visit(ReportBody body) { columns.addAll(body.getColumns()); }
}
static class PdfReportVisitor implements Visitor {
private final List<String> columns = new ArrayList<>();
@Override
public void visit(ReportHeader header) {
columns.addAll(header.getColumns());
// no extras for PDF
}
@Override
public void visit(ReportBody body) { columns.addAll(body.getColumns()); }
}
}
You could use it as follows:
public static void main(String args[]) {
ReportHeader header = new ReportHeader();
ReportBody body = new ReportBody();
List<Visitor> visitors = Arrays.asList(new PdfReportVisitor(), new ExcelReportVisitor());
visitors.forEach(each -> {
each.visit(header);
each.visit(body);
});
// do something with the visitors like `visitor.exportReport()`
}
Advantages of this approach:
- Every time you have to add a new report section to
Visitor
, it will generate a compile error in all visitor implementations. That avoids typical programming mistakes where people forget to add branches to if
or switch
statements.
- The conditional logic on how to build the report is placed in the actual implementation of the report. No need for conditional anymore.
Disadvantages of this approach:
- For sure the complexity given the additional abstractions you will be required to create.
- Sometimes it might not make sense or possible to encapsulate/group your
Column
instances into semantically cohesive objects. For example, you might end with weirdness in ReportHeader
like this:
static class ReportHeader implements Visitable {
private final List<String> columns = new ArrayList<>();
private final List<String> extras = new ArrayList<>();
private final List<String> dataThatIsOnlyUsedByExcelReport = new ArrayList<>();
@Override
public void accept(Visitor visitor) { visitor.visit(this); }
public List<String> getColumns() { return columns; }
public List<String> getExtras() { return extras; }
public List<String> getDataThatIsOnlyUsedByExcelReport() { return dataThatIsOnlyUsedByExcelReport; }
}