Collections Objects

In this blog post, I want to talk about Collections objects and how they can improve the design of a system and promote reusability.

Imagine we have a system that holds invoices received by our company. One of the requirements is to report on the total cost of invoices received in a given period. An initial implementation of this might look something like:

public class InvoiceService { 
    
    private InvoiceRepository invoiceRepository; 

    public BigDecimal calculateGrossAmountInvoiced(Date startDate, Date endDate) { 
        List<Invoice> invoices = this.invoiceRepository.findBetween(startDate, endDate); 
        BigDecimal totalGrossAmount = BigDecimal.ZERO; 

        for (Invoice invoice : invoices) { 
            totalGrossAmount = totalGrossAmount.add(invoice.getGrossAmount()); 
        } 
        return totalGrossAmount; 
    } 
}

As a first stab this seems reasonable. Over time, we find that calculating the gross amount of invoices is required in other parts of the system so we start to copy and paste the code. This results in a system rife with duplicated code that is laborious to change. For example, if it is decided that instead of gross amount, the net value is required, we have multiple copies of the code to painstakingly find and modify.

There is a solution to this problem using Collections objects. Generally, when we pass around a list of something, we should consider if we are missing a concept in our domain model and hiding information.

In the above example, we should consider passing around an object called Invoices.

Our service implementation would then become something like:

public class InvoiceService { 
    private InvoiceRepository invoiceRepository; 

    public BigDecimal calculateGrossAmountInvoiced(Date startDate, Date endDate) { 
        Invoices invoices = this.invoiceRepository.findBetween(startDate, endDate); 
        return invoices.getTotalGrossAmount(); 
    } 
}

The new invoices class holds the calculation:

private static class Invoices { 
    private final List<Invoice> invoices; 

    public Invoices(List<Invoice> invoices) { 
         this.invoices = new ArrayList<>(invoices); 
    } 

    public BigDecimal getTotalGrossAmount() { 
        BigDecimal totalGrossAmount = BigDecimal.ZERO; 

        for (Invoice invoice : invoices) { 
            totalGrossAmount = totalGrossAmount.add(invoice.getGrossAmount()); 
        } 
        return totalGrossAmount; 
    } 
}

Having an Invoices object gives us a place for logic relating to a group of invoices. Now, wherever we need to calculate the total, we call this method. If we need to change from calculating the gross amount to the net amount, it is very easy using modern tools to find all the calls to the method and change them to use the new method.

Another thing to note about the Invoices object is that it is immutable. We can hand out copies of it safely knowing that clients can’t modify it, and we don’t have to worry about threading issues.

We can add more methods to Invoices that give us a new Collections object. For example, we could add a method that filters the invoices by a given supplier reference. It then makes it very easy to calculate the gross amount for all the invoices from a given supplier, like so:

    BigDecimal total = invoices.forSupplier(supplierReference).getTotalGrossAmount();

In summary, Collections objects:

  1. Should be considered whenever we pass around a list or set of a type.
  2. Fill in gaps in our domain model.
  3. Provide a place for logic on a collection of related objects.
  4. Promote reusability of code.