0

When target Objects excelColumns, pdfColumns partly share same Objects and some of them even conditionally, what is a good OOP pattern to void functional programming, tight coupling and boilerplate like in code below? Lets assume, that there will be a lot of shared columns and only few non shared and conditional ones.

    List<Column> excelColumns = new ArrayList<>();
    List<Column> pdfColumns = new ArrayList<>();

    //shared columns
    Column test = new Column("test", 121, 11);
    excelColumns.add(test);
    pdfColumns.add(test);

    //conditional columns
    if (condition) {
        excelColumns.add(new Column("test2", 12, 21));
    }

    //non shared columns
    pdfColumns.add(new Column("test3", 12, 41));

    //shared columns
    Column test4 = new Column("test4", 12, 331);
    excelColumns.add(test4);
    pdfColumns.add(test4);
    Column test5 = new Column("test5", 72, 11);
    excelColumns.add(test5);
    pdfColumns.add(test5);
    Column test6 = new Column("test6", 82, 121);
    excelColumns.add(test6);
    pdfColumns.add(test6);
partinis
  • 139
  • 12

2 Answers2

0

You can use addAll(...) method instead add(...) for the last shared columns section to add them all for every collection. If you goal is to maintain a conditional order of inserting next columns, there is no point to obfuscate it, because here it is clearly presented.

Marcin Rzepecki
  • 578
  • 4
  • 10
  • thanks @marcin.programuje, but how to void tight coupling between `excelColumns` and `pdfColumns`, lets say I want to have implementations in different classes? – partinis Oct 31 '20 at 10:18
  • Then you can use the Strategy pattern. Assuming you want to have ```excelColumns``` collection building process separated from ```pdfColumns``` collection building process you can made a class ```ExcelColumnsBuildingStrategy``` ```PdfColumnsBuildingStrategy```. Both class will implement same interface ```ColumnsBuildingStrategy```. More info here: https://www.tutorialspoint.com/design_pattern/strategy_pattern.htm – Marcin Rzepecki Oct 31 '20 at 10:26
0

Depending on your appetite for complexity, you can do the following:

  1. Try to group related columns instances into objects like HeaderColumns, BodyColumns, etc.
  2. 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:

  1. 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.
  2. 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:

  1. For sure the complexity given the additional abstractions you will be required to create.
  2. 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; }
}
Trein
  • 3,658
  • 27
  • 36