Design Patterns: Strategy and Factory Patterns

This is the second post in my series on Design Patterns. In the first, we looked at the Builder model. We also briefly discussed the benefits of templates. If you haven’t read it yet, it might be a good idea to check out the first two paragraphs before continuing with this article.

When I sat down to start planning my next article, I was really torn between the Strategy and Factory models. I’ve used both to great effect in my code in the past and I think both are foundational patterns that belong in every OO developer’s vocabulary. It turns out that the Factory model complements the Strategy model quite well, so I decided to cover both in one article. As was the case with the Builder template we looked at last time, the Factory template is a builder template. The strategy model, on the other hand, is a behavior model.

The problem

As before, we will act as if we were part of a team of Java developers working for a bank. This time we calculate monthly interest on different types of bank accounts. Initially, we are dealing with only two types of accounts: current accounts paying 2% interest per year and savings accounts paying 4% per year. Interest will not apply to any other type of account. Our account types are defined by an enumeration.

enum AccountTypes {CURRENT, SAVINGS}

Based on these account types, we write an InterestCalculator class.

public class InterestCalculator {

    public double calculateInterest(AccountTypes accountType, double accountBalance) {
        switch (accountType) {
            case CURRENT: return accountBalance * (0.02 / 12);  //Monthly interest rate is annual rate / 12 months.
            case SAVINGS: return accountBalance * (0.04 / 12);
            default:
                return 0;
        }
    }
}


Our next requirement is to add support for two different money market accounts – a standard money market account that pays 5% per annum and a special “high-roller” money market account that pays 7.5%, but only if the client maintains a minimum balance of R100,000.00. We modify our calculator accordingly.

public class InterestCalculator {

    public double calculateInterest(AccountTypes accountType, double accountBalance) {
        switch (accountType) {
            case CURRENT: return accountBalance * (0.02 / 12);  //Monthly interest rate is annual rate / 12 months.
            case SAVINGS: return accountBalance * (0.04 / 12);
            case STANDARD_MONEY_MARKET: return accountBalance * (0.06/12);
            case HIGH_ROLLER_MONEY_MARKET: return accountBalance < 100000.00 ? 0 : accountBalance * (0.075/12);
            default:
                return 0;
        }
    }
}

It should be obvious that our code gets more complicated with each set of new requirements we implement. We have all these business rules grouped into a class that is getting harder and harder to understand. Also, rumor has it that the bank’s asset finance department has heard of our new interest calculator and would like to use it to calculate interest on loans to customers. However, their interest rates aren’t fixed – they’re tied to a central bank’s interest rates, which we’ll need to retrieve via a web service. Not only are we starting to process more account types, but the calculation logic is also getting more and more complex.

If we keep adding more and more business rules to our calculator, we’re going to end up with something that could become very difficult to maintain. Of course, we can try to extract each calculation into its own method, which might be slightly cleaner, but ultimately it’s still going to be lipstick on a pig.

The problem we have is this:

  • We have a unique, convoluted and difficult to maintain method that tries to cope with a number of different scenarios.

The Strategy model can help us solve this problem.

The reasons)

The strategy model allows us to dynamically swap algorithms (i.e. application logic) at runtime. In our scenario, we want to change the logic used to calculate interest, depending on the type of account we are working with.

Our first step is to define an interface to identify the input and output of our calculations – that is, the account balance and the interest on that balance.

interface InterestCalculationStrategy {

    double calculateInterest(double accountBalance);  //Note the absence of an access modifier - interface methods are implicitly public.

}

Note that our interface is only concerned with account balance – it doesn’t care about account type, since each implementation will already be specific to a particular account type.

The next step is to create strategies to deal with each of our calculations.

class CurrentAccountInterestCalculation implements InterestCalculationStrategy {

    @Override
    public double calculateInterest(double accountBalance) {
        return accountBalance * (0.02 / 12);
    }
}

class SavingsAccountInterestCalculation implements InterestCalculationStrategy {

    @Override
    public double calculateInterest(double accountBalance) {
        return accountBalance * (0.04 / 12);
    }
}

class MoneyMarketInterestCalculation implements InterestCalculationStrategy {

    @Override
    public double calculateInterest(double accountBalance) {
        return accountBalance * (0.06/12);
    }
}

class HighRollerMoneyMarketInterestCalculation implements InterestCalculationStrategy {

    @Override
    public double calculateInterest(double accountBalance) {
        return accountBalance < 100000.00 ? 0 : accountBalance * (0.075/12)
    }
}

Each calculation is now isolated in its own class, making it much easier to understand individual calculations – they are no longer surrounded by clutter. Next, we’ll refactor our calculator.

public class InterestCalculator {

    //Strategies for calculating interest.
    private final InterestCalculationStrategy currentAccountInterestCalculationStrategy = new CurrentAccountInterestCalculation();
    private final InterestCalculationStrategy savingsAccountInterestCalculationStrategy = new SavingsAccountInterestCalculation();
    private final InterestCalculationStrategy moneyMarketAccountInterestCalculationStrategy = new MoneyMarketInterestCalculation();
    private final InterestCalculationStrategy highRollerMoneyMarketAccountInterestCalculationStrategy = new HighRollerMoneyMarketInterestCalculation();


    public double calculateInterest(AccountTypes accountType, double accountBalance) {
        switch (accountType) {
            case CURRENT: return currentAccountInterestCalculationStrategy.calculateInterest(accountBalance);
            case SAVINGS: return savingsAccountInterestCalculationStrategy.calculateInterest(accountBalance);
            case STANDARD_MONEY_MARKET: return moneyMarketAccountInterestCalculationStrategy.calculateInterest(accountBalance);
            case HIGH_ROLLER_MONEY_MARKET: return highRollerMoneyMarketAccountInterestCalculationStrategy.calculateInterest(accountBalance);
            default:
                return 0;
        }
    }
}

We’ve moved the calculation logic out of the calculator itself, but the code still doesn’t look great – it still seems like there’s too much going on in one method. I would even go so far as to call it ugly (but I’m known to be a pedant). Luckily, there’s an easy way to clean up this mess – the factory template.

The Factory pattern allows us to create objects without necessarily knowing or caring about the type of objects we are creating. This is exactly what our calculator needs – we want calculations, but we don’t care about the details of those calculations. All we really need is a reference to a strategy that knows how to perform the appropriate interest calculation for a particular type of account. We can set up our factory as follows:

class InterestCalculationStrategyFactory {

    private final InterestCalculationStrategy currentAccountInterestCalculationStrategy = new CurrentAccountInterestCalculation();
    private final InterestCalculationStrategy savingsAccountInterestCalculationStrategy = new SavingsAccountInterestCalculation();
    private final InterestCalculationStrategy moneyMarketAccountInterestCalculationStrategy = new MoneyMarketInterestCalculation();
    private final InterestCalculationStrategy highRollerMoneyMarketAccountInterestCalculationStrategy = new HighRollerMoneyMarketInterestCalculation();

    //A factory can create a new instance of a class for each request, but since our calculation strategies are stateless,
    //we can hang on to a single instance of each strategy and return that whenever someone asks for it.
    public InterestCalculationStrategy getInterestCalculationStrategy(AccountTypes accountType) {
        switch (accountType) {
            case CURRENT: return currentAccountInterestCalculationStrategy;
            case SAVINGS: return savingsAccountInterestCalculationStrategy;
            case STANDARD_MONEY_MARKET: return moneyMarketAccountInterestCalculationStrategy;
            case HIGH_ROLLER_MONEY_MARKET: return highRollerMoneyMarketAccountInterestCalculationStrategy;
            default: return null;
        }
    }
}

You might think it looks a lot like what we had before. It is, but all account-type specific logic is now encapsulated in a class that satisfies the single-responsibility principle. The factory is not concerned with the calculations – all it does is match the account types to the appropriate strategies. As a result, we can greatly simplify the code in our calculator class.

public class InterestCalculator {

    private final InterestCalculationStrategyFactory interestCalculationStrategyFactory = new InterestCalculationStrategyFactory();

    public double calculateInterest(AccountTypes accountType, double accountBalance) {
        InterestCalculationStrategy interestCalculationStrategy = interestCalculationStrategyFactory.getInterestCalculationStrategy(accountType);

        if (interestCalculationStrategy != null) {
            return interestCalculationStrategy.calculateInterest(accountBalance);
        } else {
            return 0;
        }
    }
}

It looks much better than before, but there’s still a part of the code that bugs me – that nasty null check. Let’s do another refactoring – we’ll introduce a null object (also known as a special case) to handle unexpected account types. It just means that we will have a default policy that will be applied as a last resort. It looks like this.

class NoInterestCalculation implements InterestCalculationStrategy {

    @Override
    public double calculateInterest(double accountBalance) {
        return 0;
    }
}

We can now add No interest calculation at our factory.

class InterestCalculationStrategyFactory {

    private final InterestCalculationStrategy currentAccountInterestCalculationStrategy = new CurrentAccountInterestCalculation();
    private final InterestCalculationStrategy savingsAccountInterestCalculationStrategy = new SavingsAccountInterestCalculation();
    private final InterestCalculationStrategy moneyMarketAccountInterestCalculationStrategy = new MoneyMarketInterestCalculation();
    private final InterestCalculationStrategy highRollerMoneyMarketAccountInterestCalculationStrategy = new HighRollerMoneyMarketInterestCalculation();
    private final InterestCalculationStrategy noInterestCalculationStrategy = new NoInterestCalculation();

    //A factory can create a new instance of a class for each request, but since our calculation strategies are stateless,
    //we can hang on to a single instance of each strategy and return that whenever someone asks for it.
    public InterestCalculationStrategy getInterestCalculationStrategy(AccountTypes accountType) {
        switch (accountType) {
            case CURRENT: return currentAccountInterestCalculationStrategy;
            case SAVINGS: return savingsAccountInterestCalculationStrategy;
            case STANDARD_MONEY_MARKET: return moneyMarketAccountInterestCalculationStrategy;
            case HIGH_ROLLER_MONEY_MARKET: return highRollerMoneyMarketAccountInterestCalculationStrategy;
            default: return noInterestCalculationStrategy;
        }
    }
}

Now that our factory will no longer return null values, we can refactor the calculator again. The final version looks like this.

public class InterestCalculator {

    private final InterestCalculationStrategyFactory interestCalculationStrategyFactory = new InterestCalculationStrategyFactory();

    public double calculateInterest(AccountTypes accountType, double accountBalance) {
        InterestCalculationStrategy interestCalculationStrategy = interestCalculationStrategyFactory.getInterestCalculationStrategy(accountType);

        return interestCalculationStrategy.calculateInterest(accountBalance);
    }
}

We’ve effectively removed 75% of the code in the calculator class, and we won’t have to come back and edit it, no matter how many new strategies we decide to add. Good, clean, simple!

Summary

In this article, we looked at a sample code that became too complex because it had to change its logic depending on the conditions under which it was run (i.e. different interest calculations for different types of accounts). We then extracted the different elements of logic into their own strategies. Despite this, our code was still quite complex, as it knew all the different strategies that could potentially be used. We solved this problem by creating a factory to encapsulate the logic concerned with selecting appropriate strategies for various conditions. Finally, we replaced a null check with a null object, which allowed us to simplify our code even further.

As always, feel free to leave a comment if you have any questions/comments/suggestions.

Abdul J. Gaspar