Strategy vs. Factory Design Patterns in Java
Want to learn more about using strategy and factory patterns in Java? Check out this tutorial to learn how to use both on your next project!
Join the DZone community and get the full member experience.
Join For FreeHere I am with my another article on design patterns. Many of us use strategy as well as factory design patterns. But, sometimes, it becomes really hard to tell the difference between the two and to decide when and which would be a better fit for the project at hand. Let's take a look at some of the key points for each of these with an example.
Strategy Design Pattern
The strategy design pattern (also known as the policy design pattern) is a behavioral design pattern that allows us to select an algorithm at runtime. In this pattern, the code receives run-time instructions to decide which group of the algorithms to use.
Strategy design patterns make the algorithm vary independently from the context, clients, and codes that use it.
For example, a validating object that performs validation on data received may use the strategy pattern to select a validation algorithm, depending on the type, source of data, or based on other user parameters.
These validation algorithms will be written separately from the validating object and, hence, can easily be used by different validating objects without any code duplication. Any validating object can also use different validation algorithms, as well, based on type, source, or user input.
The strategy pattern stores a reference to some algorithm or code and supplies it wherever required.
So, in short, the strategy design pattern is one of the many defined algorithms in the design pattern family, which may be applied or used on data.
The ability to decide which algorithm to use at runtime allows the calling or client code to be more flexible and reusable and avoids code duplication.
The client or context does not know which strategy (algorithm) it has to use.
Factory Design Pattern
The factory design pattern is a creational design pattern, which provides one of the best ways to create objects. This pattern uses factory methods to deal with the problem of creating objects without specifying the exact class of the object that it has to create.
In factory patterns, we create objects by calling a factory method rather than by calling a constructor.
The factory pattern is one of the most used design patterns in Java.
The object creation logic is:
Implemented either in an interface or implemented by child classes.
Or, it is implemented in a base class and optionally overridden by derived classes.
In the factory design pattern, we create an object without exposing the creation logic to the client.
So, in short, the factory pattern gives the applicable object from the family of classes that we can use. This object represents an algorithm as well as lots of other functionalities.
In the below example, I will further attempt to highlight some of the differences. I am keeping the example relatively easy to understand and keep the focus strictly on the coding style. Here, I am using an example of account-management:
Account-Management Example With the Strategy Pattern
We have various strategies for calculating the interest amount on the principal amount saved in the account.
So, first, we have to create an interface to define the layout of the strategy (algorithm). I am creating the InterestCalculationStrategy
interface.
package design.strategy;
public interface InterestCalculationStrategy {
double calculateInterest(double principal, double rate, int term);
}
Now, I am defining two flavors of the interest calculation logic or strategy algorithm,
SimpleInterestCalculator
, to calculate simple interest for the defined rate and given term.
package design.strategy;
public class SimpleInterestCalculator implements InterestCalculationStrategy {
@Override
public double calculateInterest(final double principal, final double rate, final int term) {
return ((principal * term * rate) / 100);
}
@Override
public String toString() {
return "Simple Interest";
}
}
And, I will use the CompoundInterestCalculator
to calculate compound interest for the defined rate and given term.
package design.strategy;
public class CompundInterestCalculator implements InterestCalculationStrategy {
@Override
public double calculateInterest(final double principal, final double rate, final int term) {
return (principal * Math.pow(1.0 + rate/100.0, term) - principal);
}
@Override
public String toString() {
return "Compound Interest";
}
}
We have two account types: Saving or Current. The rate of interest is fixed and defined based on this account type. Here, please notice that I have fixed the rate of interest based on the account type.
package design.strategy;
public enum AccountType {
SAVING (2.0d),
CURRENT (1.0d);
private double rate;
AccountType (final double rate) {
this.rate = rate;
}
public double getRate() {
return rate;
}
}
Then, we have a model object to hold the Account
details, as shown below:
package design.strategy;
public class Account {
private long accountNo;
private String accountHolderName;
private AccountType accountType;
private InterestCalculationStrategy interestStrategy;
private double amount;
public Account() {
super();
}
public Account(long accountNo, String accountHolderName, AccountType accountType) {
this();
this.accountNo = accountNo;
this.accountHolderName = accountHolderName;
this.accountType = accountType;
}
public Account(long accountNo, String accountHolderName, AccountType accountType,
InterestCalculationStrategy interestStrategy) {
this(accountNo, accountHolderName, accountType);
this.interestStrategy = interestStrategy;
}
public long getAccountNo() {
return accountNo;
}
public void setAccountNo(long accountNo) {
this.accountNo = accountNo;
}
public String getAccountHolderName() {
return accountHolderName;
}
public void setAccountHolderName(String accountHolderName) {
this.accountHolderName = accountHolderName;
}
public AccountType getAccountType() {
return accountType;
}
public void setAccountType(AccountType accountType) {
this.accountType = accountType;
}
public InterestCalculationStrategy getInterestStrategy() {
return interestStrategy;
}
public void setInterestStrategy(InterestCalculationStrategy interestStrategy) {
this.interestStrategy = interestStrategy;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public void deposit(double amount) {
// check for only positive/valid amount
if (amount > 0.0d) {
this.amount += amount;
}
}
public void withdraw(double amount) {
// check for only positive/valid amount and also for below than the available amount in account
if (amount > 0.0d && amount < this.amount) {
this.amount -= amount;
}
}
public double getInterest(int term) {
if (getInterestStrategy() != null && getAccountType() != null) {
return getInterestStrategy().calculateInterest(getAmount(), getAccountType().getRate(), term);
}
return 0.0d;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Account [accountNo=").append(getAccountNo())
.append(", accountHolderName=").append(getAccountHolderName())
.append(", accountType=").append(getAccountType())
.append(", rate=").append((getAccountType() != null) ? getAccountType().getRate() : 0.0d)
.append(", interestStrategy=").append(getInterestStrategy())
.append(", amount=").append(getAmount()).append("]");
return builder.toString();
}
}
Now, we will test the strategy pattern via the below code:
package design.strategy;
public class Main {
public static void main(String[] args) {
Account acct1 = new Account(12345678l, "Vijay Kumar", AccountType.SAVING);
acct1.setInterestStrategy(new CompundInterestCalculator());
acct1.deposit(10000.0d);
System.out.print(acct1);
System.out.println(" has interest : " + acct1.getInterest(5));
Account acct2 = new Account(12345680l, "Jay Kumar", AccountType.SAVING);
acct2.setInterestStrategy(new SimpleInterestCalculator());
acct2.deposit(10000.0d);
System.out.print(acct2);
System.out.println(" has interest : " + acct2.getInterest(5));
}
}
Please notice here that both of the accounts are part of the Saving type, and we are using different interest calculation algorithms ( Compound
or Simple
) based on our choice. Basically, algorithms are free to use with the context (account) loosely coupled. This is the benefit of using the strategy design pattern.
We can also create a factory for the strategy (StrategyFactory
) here like below:
package design.strategy;
public class StrategyFactory {
public InterestCalculationStrategy createStrategy(String strategyType) {
InterestCalculationStrategy strategy = null;
if (strategyType != null) {
if ("COMPOUND".equalsIgnoreCase(strategyType)) {
strategy = new CompundInterestCalculator();
} else if ("SIMPLE".equalsIgnoreCase(strategyType)) {
strategy = new SimpleInterestCalculator();
} else {
System.err.println("Unknown/unsupported strategy-type");
}
}
return strategy;
}
}
In that case, our code to test strategy pattern will look like below:
package design.strategy;
public class Main1 {
public static void main(String[] args) {
StrategyFactory factory = new StrategyFactory();
Account acct1 = new Account(12345678l, "Vijay Kumar", AccountType.SAVING);
acct1.setInterestStrategy(factory.createStrategy("COMPOUND"));
acct1.deposit(10000.0d);
System.out.print(acct1);
System.out.println(" has interest : " + acct1.getInterest(5));
Account acct2 = new Account(12345680l, "Jay Kumar", AccountType.SAVING);
acct2.setInterestStrategy(factory.createStrategy("SIMPLE"));
acct2.deposit(10000.0d);
System.out.print(acct2);
System.out.println(" has interest : " + acct2.getInterest(5));
}
}
Below is the output for the program using the factory pattern:
Account [accountNo=12345678, accountHolderName=Vijay Kumar, accountType=SAVING, rate=2.0, interestStrategy=Compound Interest, amount=10000.0] has interest : 1040.8080320000008
Account [accountNo=12345680, accountHolderName=Jay Kumar, accountType=SAVING, rate=2.0, interestStrategy=Simple Interest, amount=10000.0] has interest : 1000.0
Account-Management Example Using the Factory Pattern
We have the AccountType
interface to define account-types with the pre-defined or fixed rate of interest.
package design.factory;
public enum AccountType {
SAVING (2.0d),
CURRENT (1.0d);
private double rate;
AccountType (final double rate) {
this.rate = rate;
}
public double getRate() {
return rate;
}
}
I am implementing an abstract base class for the account and creating the various flavors of Account
by sub-classing it.
Below is the code for the Account
. Please note that I have defined the class as abstract
to force the sub-classing of it.
package design.factory;
public abstract class Account {
private long accountNo;
private String accountHolderName;
private AccountType accountType;
private String interestStrategy;
private double amount;
public Account() {
super();
}
public Account(long accountNo, String accountHolderName, AccountType accountType) {
this();
this.accountNo = accountNo;
this.accountHolderName = accountHolderName;
this.accountType = accountType;
}
public long getAccountNo() {
return accountNo;
}
public void setAccountNo(long accountNo) {
this.accountNo = accountNo;
}
public String getAccountHolderName() {
return accountHolderName;
}
public void setAccountHolderName(String accountHolderName) {
this.accountHolderName = accountHolderName;
}
public AccountType getAccountType() {
return accountType;
}
public void setAccountType(AccountType accountType) {
this.accountType = accountType;
}
public String getInterestStrategy() {
return interestStrategy;
}
public void setInterestStrategy(String interestStrategy) {
this.interestStrategy = interestStrategy;
}
public double getAmount() {
return amount;
}
public void setAmount(double amount) {
this.amount = amount;
}
public void deposit(double amount) {
// check for only positive/valid amount
if (amount > 0.0d) {
this.amount += amount;
}
}
public void withdraw(double amount) {
// check for only positive/valid amount and also for below than the available amount in account
if (amount > 0.0d && amount < this.amount) {
this.amount -= amount;
}
}
// this need to be defined by the sub-classes by applying right algorithm
public abstract double getInterest(int term);
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("Account [accountNo=").append(getAccountNo())
.append(", accountHolderName=").append(getAccountHolderName())
.append(", accountType=").append(getAccountType())
.append(", rate=").append((getAccountType() != null) ? getAccountType().getRate() : 0.0d)
.append(", interestStrategy=").append(getInterestStrategy())
.append(", amount=").append(getAmount()).append("]");
return builder.toString();
}
}
Now, we have to create the types for the account. I create the SavingAccount,
which is tied up with the compound interest algorithm.
package design.factory;
public class SavingAccount extends Account {
public SavingAccount(long accountNo, String accountHolderName) {
super(accountNo, accountHolderName, AccountType.SAVING);
setInterestStrategy("Compound Interest");
}
@Override
public double getInterest(int term) {
if (this.getAccountType() != null) {
return this.getAmount() * Math.pow(1.0 + this.getAccountType().getRate()/100.0, term) - this.getAmount();
}
return 0.0d;
}
}
Next, I created the CurrentAccount
, which is tied up with the simple-interest algorithm.
package design.factory;
public class CurrentAccount extends Account {
public CurrentAccount(long accountNo, String accountHolderName) {
super(accountNo, accountHolderName, AccountType.CURRENT);
setInterestStrategy("Simple Interest");
}
@Override
public double getInterest(int term) {
if (this.getAccountType() != null) {
return ((this.getAmount() * term * this.getAccountType().getRate()) / 100);
}
return 0.0d;
}
}
So, basically, the types of accounts not only have pre-defined rates (as in the strategy) but also the pre-defined interest calculation algorithms, which are tightly coupled. Therefore, the account instance will have defined functionality.
Now, let's take a look at the most important step. We have to define the factory class (AccountFactory
) for the Account
based on the given account-type.
package design.factory;
public class AccountFactory {
public Account createAccount(long accountNo, String accountHolderName, String accountType) {
Account account = null;
AccountType type = AccountType.valueOf(accountType);
if (type != null) {
switch (type) {
case SAVING:
account = new SavingAccount(accountNo, accountHolderName);
break;
case CURRENT:
account = new CurrentAccount(accountNo, accountHolderName);
break;
default:
// if we create any new account-type but failed to define the class for Account
System.err.println("Unknown/unsupported account-type.");
}
} else {
System.err.println("Undefined account-type.");
}
return account;
}
}
And, now, at last, let's look at the code to test the factory pattern.
package design.factory;
public class Main {
public static void main(String[] args) {
AccountFactory factory = new AccountFactory();
Account acct1 = factory.createAccount(12345678l, "Vijay Kumar", "SAVING");
acct1.deposit(10000.0d);
System.out.print(acct1);
System.out.println(" has interest : " + acct1.getInterest(5));
Account acct2 = factory.createAccount(12345680l, "Jay Kumar", "CURRENT");
acct2.deposit(10000.0d);
System.out.print(acct2);
System.out.println(" has interest : " + acct2.getInterest(5));
}
}
Below is the output for the program using the factory pattern:
Account [accountNo=12345678, accountHolderName=Vijay Kumar, accountType=SAVING, rate=2.0, interestStrategy=Compound Interest, amount=10000.0] has interest : 1040.8080320000008
Account [accountNo=12345680, accountHolderName=Jay Kumar, accountType=CURRENT, rate=1.0, interestStrategy=Simple Interest, amount=10000.0] has interest : 500.0
Well, there you have it! I hope this tutorial helped demonstrate the difference between strategy and factory patterns.
Liked the article? Please don't forget to press that like button. Happy coding!
Need more articles on Design Patterns? Below are some of them I have shared with you.
Some additional Articles:
Opinions expressed by DZone contributors are their own.
Comments