Refactoring to design patterns- The template pattern

jerry

December 22, 2016

The template method pattern is a useful design pattern when you have a bunch of classes which follow a similar algorithm/steps but vary in the implementation of the steps.

Let's take a look a simple example where refactoring to this pattern improves the overall maintainability of the code.

PDF generation

Before refactoring

We had a code that generates PDF from a report. Here is the class which does that:

class ReportPDFGenerator
  def generate(data)
    add_encoding(data)
    # Linear PDF report Generation Code
  end

  def add_encoding(pdf)
    # Add encoding if requested
    # else return the same PDF
  end
end

Later, we introduced another type of report called complex report, where the way of generating the report is entirely different. We wrote a class for that like this:

class ComplexReportPDFGenerator
  def generate(data)
    add_encoding(data)
    # Complex PDF Generation Code
  end

  private

  def add_encoding(data)
    # Add encoding if requested
    # else return the same PDF
  end
end

Refactoring this into the template pattern to remove some duplication, more importantly, to easily support another requirement where there is a slight change in how the PDF generation varies.

After refactoring
class ReportPDFGenerator
  def initialize(data)
    @data = data
  end

  def generate(data)
    add_encoding(data)
    generate_pdf(data)
  end

  private

  def add_encoding(data)
    # default implementation
  end

  def generate_pdf(data)
    #default implementation
  end
end

Linear Report PDF Generator:

class LinearReportPDFGenerator < ReportPdfGenerator
  def generate_pdf(data)
    # Linear PDF Generation Code
  end

Complex Report PDF Generator:

class ComplexReportPDFGenerator < ReportPDFGenerator
  def generate_pdf(data)
    # complex pdf Generation Code
  end
end

Now that we are aware of what template patterns mean, let's take a look at another slightly more complicated scenario. Both these examples were taken from our codebases, it is just that we have removed some details that are not relevant.

The following example is taken from a micro-financing application that we work on.

Before refactoring
class BankAccountDepositDisburser
  def initialize(loan)
    @loan = loan
    @loan_validator = LoanValidator.new @loan
    @loan_agreement_validator = LoanAgreementValidator.new @loan.loan_agreement

    @bank_deposit_validator = BankAccountDepositValidator.new(@loan.bank_account_deposit)
  end

  def disburse
    if valid?
      @loan.disburse!
    end
  end

  def valid?
    [@loan_validator.valid?, @loan_agreement_validator.valid?, @bank_deposit_validator.valid?].all?
  end
end

class ACHDepositDisburser
  def initialize(loan)
    @loan = loan
    @loan_validator = LoanValidator.new @loan
    @loan_agreement_validator = LoanAgreementValidator.new @loan.loan_agreement

    @ach_deposit_validator = AchDepositValidator.new @loan.ach_deposit
  end

  def disburse
    if valid?
      @loan.disburse!
    end
  end

  def valid?
    [@loan_validator.valid?, @loan_agreement_validator.valid?, @ach_deposit_validator.valid?].all?
  end
end

In the above snippet, you can see that both these class are in charge of disbursing loans, it just that the validations that need to run before we can disburse these loans are different. There are also some default validations that needs to run. This seems like a good opportunity to refactor to Template pattern.

After refactoring
class LoanDisburser
  def initialize(loan)
    @loan = loan
    initialize_validators
  end

  def disburse
    @loan.disburse! if valid?
  end

  def initialize_validators
    @validators = []
    @validators << LoanValidator.new(@loan)
    @validators << LoanAgreementValidator.new(@loan.loan_agreement)
  end

  def valid?
    @validators.map(&:valid?).all?
  end
end

class BankAccountDepositDisburser < LoanDisburser
  def initialize_validators
    super
    @validators << BankAccountDepositValidator.new(@loan.bank_account_deposit)
  end
end

class ACHDepositDisburser < LoanDisburser
  def initialize_validators
    super
    @validators << AchDepositValidator.new(@loan.ach_deposit)
  end
end

Summary

As you have already seen template pattern is a useful design pattern to use when you have classes that go through a similar sequence of steps but vary in how the step are implemented.