Abstract Factory Design Pattern in Java

Abstract Factory Design Pattern in Java

In this article, I am going to discuss the Abstract Factory Design Pattern in Java with Examples. Please read our previous article where we discussed the Factory Design Pattern in Java with Examples. The Abstract Factory Design Pattern belongs to the creational design pattern category and is one of the most used design patterns in real-world applications.

What is an Abstract Factory Design Pattern?

In software development, there are scenarios where multiple related objects need to be created together or in a consistent manner. The Abstract Factory design pattern provides a solution by encapsulating the creation of families of related objects within a factory hierarchy. By abstracting the creation process, the pattern promotes loose coupling, enhances flexibility, and simplifies the management of object creation.

In this article, we will explore the fundamental principles, advantages, and potential disadvantages of the Abstract Factory design pattern, emphasizing its significance in creating families of related objects and supporting modular design.

The Abstract Factory design pattern is a creational pattern that provides an interface for creating families of related objects, without specifying their concrete classes. It introduces an abstract factory class that defines a set of methods for creating different types of objects. Concrete factory classes implement these methods to create objects that belong to a particular family or variant. The client code interacts with the abstract factory and relies on the factory’s methods to create instances of objects. This decouples the client code from the concrete implementations, allowing for easy substitution of object families without affecting the client code. The pattern ensures that all created objects are compatible and consistent within the same family.

According to the Gang of Four Definition: The Abstract Factory Design Pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.

In simple words, we can say that the Abstract Factory is a super factory that creates other factories. This Abstract Factory is also called the Factory of Factories. That means the Abstract Factory design pattern provides an interface for creating families of related or dependent products but leaves the actual object creation to the concrete factory classes. If this is not clear at the moment, then don’t worry, we will explain this with one Real-time Example.

Advantages of Abstract Factory Design Pattern in Java

Some of the advantages of using the abstract factory pattern are:

  • Encapsulation and Abstraction: The Abstract Factory pattern promotes encapsulation by encapsulating the creation process of related objects within a separate factory hierarchy. This encapsulation hides the creation details from the client code, providing a clear separation of concerns and enhancing code maintainability. Clients interact with the abstract factory’s interface, which provides a consistent way to create objects of a specific family.
  • Loose Coupling and Modularity: The Abstract Factory pattern promotes loose coupling by decoupling the client code from the concrete implementations of the objects. Clients interact with the abstract factory and rely on its interface, without being aware of the specific concrete classes being instantiated. This loose coupling allows for easy substitution of object families or variants, facilitating modularity and enhancing flexibility in the system.
  • Consistency and Compatibility: The Abstract Factory pattern ensures that all objects created by a specific concrete factory are compatible and consistent within the same family or variant. The factory guarantees that the created objects work together seamlessly, adhering to a common interface or base class. This consistency simplifies the development process, as the client code can rely on the assumptions and behavior shared by objects within the same family.
  • Easy Configuration and Extensibility: The Abstract Factory pattern facilitates easy configuration and extensibility of the system. By swapping the concrete factory implementation, developers can easily switch between different families of objects or introduce new variants without modifying the client code. This flexibility allows for dynamic adaptation to changing requirements and supports the Open-Closed Principle.
  • Dependency Inversion and Testability: The Abstract Factory pattern promotes the Dependency Inversion Principle by relying on abstractions and interfaces. Clients depend on the abstract factory and its interface, rather than concrete implementations. This abstraction facilitates the substitution of objects and improves testability, as the client code can be tested in isolation by using mock or stub implementations of the abstract factory.
Disadvantages of Abstract Factory Design Pattern in Java

Some of the disadvantages of using the abstract factory pattern are:

  • Increased Complexity: The Abstract Factory pattern introduces an additional layer of abstraction and complexity to the codebase. The presence of abstract factory classes, concrete factory implementations, and multiple product families may complicate the overall design and understanding of the system. Developers must carefully design and manage the abstract factory hierarchy and its relationships with other components to avoid unnecessary complexity.
  • Limited Flexibility in Adding New Product Families: The Abstract Factory pattern requires a predefined set of product families and their corresponding factories. Adding new product families to the system may require modifying the abstract factory interface and implementing new concrete factory classes. This limitation can pose challenges in cases where the system needs to support the dynamic creation of new families without modifying existing code.
  • Reduced Transparency: Using the Abstract Factory pattern can reduce transparency in the codebase. Clients rely on the abstract factory’s interface, which may hide the specific details of object creation. This reduced transparency can make it harder to trace and understand the creation process of objects within the system.
  • Increased Code Complexity: The introduction of the abstract factory hierarchy and the need for multiple concrete factory implementations can increase the overall code complexity. Managing relationships between the factories and ensuring consistency across families may require additional effort in design and maintenance.

Note: The Abstract Factory design pattern offers valuable advantages in encapsulating the creation of families of related objects, promoting loose coupling, enhancing flexibility and extensibility, ensuring consistency and compatibility, and encouraging dependency inversion. Its benefits include encapsulation and abstraction, loose coupling and modularity, consistency and compatibility, easy configuration and extensibility, and adherence to the Dependency Inversion Principle. However, it is important to consider potential drawbacks, including increased complexity, limited flexibility in adding new product families, reduced transparency, and increased code complexity. By carefully evaluating the requirements of the system and considering trade-offs, developers can leverage the Abstract Factory pattern to create families of objects in a flexible and maintainable manner, supporting the modular design and facilitating future enhancements.

Example to Understand Abstract Factory Design Patterns in Java

Let’s explore a real-world example where the Abstract Factory pattern can be applied: a banking company. In this scenario, the company produces different types of products, each with its own features. The Abstract Factory pattern can be used to handle the creation of these variant-specific components.

The company can define an abstract class called “DepositFactory” that represents the interface for creating the phone components. It will have methods like “getInterestRate()” and “calculateWithdrawalAmount()”. Subclasses of DepositFactory, such as “Savings”, “FixedDeposit,” and “RecurringDeposit,” will be responsible for implementing these methods based on the specific components required for each variant.

By using the Abstract Factory pattern, the banking company can achieve several advantages. Firstly, it provides a way to enforce consistency among the components within a variant, ensuring that only compatible components are used together. This helps in maintaining quality and reliability. Secondly, it enables easy extensibility, allowing the company to introduce new variants or replace components without modifying the client code. Moreover, the pattern promotes code flexibility and scalability, as new factories can be added to support new product lines without impacting the existing codebase.

This can be described using the following UML:

Abstract Factory Design Pattern in Java with Examples

Implementing Abstract Factory Design Pattern in Java

Step 1: Create a new directory to store all the class files of this project.

Step 2: Open VS Code and create a new project, called abstractfactorypattern.

Step 3: In the project, create a new file called Bank.java.

Step 4: Add the following contents to the file:

What is a Abstract Factory Design Pattern?

This is an interface, that will later be implemented by other (child) classes.

Step 5: Create a file called BankA.java in the project.

Step 6: Populate it with the following code:

Advantages of Abstract Factory Design Pattern in Java

We have added the following code:

  • Created a class called BankA. This class is said to be the child class of Bank.
  • Implemented the getBankName() function defined in the parent class. Note that we use the @Override annotation. This is to indicate that if an implementation of getBankName () is present in the parent class, that implementation is to be overridden with this one.

Step 7: Create a file called BankB.java in the project.

Step 8: Populate it with the following code:

Disadvantages of Abstract Factory Design Pattern in Java

All the code additions are similar to those done for the BankA.java file.

Step 9: Create a file called BankC.java in the project.

Step 10: Populate it with the following code:

Example to Understand Abstract Factory Design Patterns in Java

All the code additions are similar to those done for the BankA.java file.

Step 11: Create a file called SavingsAccount.java in the project.

Step 12: Populate it with the following code:

Implementing Abstract Factory Design Pattern in Java

We have added the following code:

  • Created a class called SavingsAccount. This class is said to be the child class of Deposit.
  • Implemented the getInterestRate() function defined in the parent class. Note that we use the @Override annotation. This is to indicate that if an implementation of getInterestRate() is present in the parent class, that implementation is to be overridden with this one.

Step 13: Create a file called FixedDeposit.java in the project.

Step 14: Populate it with the following code:

Abstract Factory Pattern in Java with Examples

All the code additions are similar to those done for the SavingsAccount.java file.

Step 15: Create a file called RecurringDeposit.java in the project.

Step 16: Populate it with the following code:

What is a Abstract Factory Pattern?

All the code additions are similar to those done for the SavingsAccount.java file.

Step 17: Create a file called AbstractFactory.java in the project.

Step 18: Populate it with the following code:

Advantages of Abstract Factory Pattern in Java

We have performed the following code additions:

  • Created an abstract class called AbstractFactory.
  • Added two function definitions.

Step 19: Create a file called BankFactory.java in the project.

Step 20: Populate it with the following code:

Disadvantages of Abstract Factory Pattern in Java

We have performed the following code additions:

  • Created a class called BankFactory and extended it from the AbstractFactory class.
  • Created implementations for the functions defined in the AbstractFactory class. Note that the getDeposit() function returns null. This is because this file is only used for the getBank() function. The getDeposit() function will be implemented in the DepositFactory.java class.

Step 21: Create a file called DepositFactory.java in the project.

Step 22: Populate it with the following code:

Example to Understand Abstract Factory Patterns in Java

We have performed the following code additions:

  • Created a class called DepositFactory and extended it from the AbstractFactory class.
  • Created implementations for the functions defined in the AbstractFactory class. Note that the getBank() function returns null. This is because this file is only used for the getDeposit() function.

Step 23: Create a file called FactoryCreator.java in the project.

Step 24: Populate it with the following code:

Implementing Abstract Factory Pattern in Java

We have performed the following code additions:

  • Created a class FactoryCreator.
  • Added a function that returns the appropriate factory, based on the choice entered by the user.

Step 25: Create a file called AbstractFactoryPatternExample.java in the project. This class will contain the main() function.

Step 26: Populate it with the following code:

Abstract Factory Design Pattern in Java

We have performed the following code additions:

  • Imported the required packages
  • Added the main function
    • On line 9: Created an object of type BufferedReader to obtain user input.
    • From lines 11 to 13: Created an object of type Bank, based on user input.
    • From lines 15 to 22: Obtained various information about the deposit from the user.
    • From lines 24 to 26: Creates an object of type deposit based on user input. Fed it with the details obtained from the user (in lines 15 to 22).
    • On line 27: Calculated the withdrawal amount and printed it to the user.

Step 27: Compile and execute the application. Ensure compilation is successful. This is the sample execution of the program:

Abstract Factory Pattern in Java

Congratulations! You now know how to implement the factory pattern in Java!

The Complete Example Code
AbstractFactory.java
public abstract class AbstractFactory
{
    public abstract Bank getBank(String bank);
    public abstract Deposit getDeposit (String deposit);
}
AbstractFactoryPatternExample.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class AbstractFactoryPatternExample
{
    public static void main(String[] args) throws IOException
    {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        AbstractFactory bankAbstractFactory = FactoryCreator.getFactory("bank");
        System.out.print("Where would you like to deposit your money: ");
        Bank bank = bankAbstractFactory.getBank(br.readLine());

        System.out.print("Enter the amount: ");
        double amount = Double.parseDouble(br.readLine());

        System.out.print("Enter the tenure in years: ");
        int years = Integer.parseInt(br.readLine());

        System.out.print("Enter the interest rate for" + bank.getBankName() + ": ");
        double rate = Double.parseDouble(br.readLine());

        System.out.print("Which type of deposit?: ");
        Deposit deposit = (FactoryCreator.getFactory("deposit")).getDeposit(br.readLine());
        deposit.getInterestRate(rate);
        System.out.println("Your withdrawal amount will be: " + deposit.calculateWithdrawalAmount(amount, years));
    }
}
Bank.java
public interface Bank
{
    public String getBankName();
}
BankA.java
public class BankA implements Bank
{
    private String bankName = "Bank A";

    @Override
    public String getBankName()
    {
        return bankName;
    }
}
BankB.java
public class BankB implements Bank
{
    private String bankName = "Bank B";

    @Override
    public String getBankName()
    {
        return bankName;
    }
}
BankC.java
public class BankC implements Bank
{
    private String bankName = "Bank C";

    @Override
    public String getBankName()
    {
        return bankName;
    }
}
BankFactory.java
public class BankFactory extends AbstractFactory
{

    @Override
    public Bank getBank(String bank)
    {
        if (bank.equalsIgnoreCase("bank a"))        return new BankA();
        else if (bank.equalsIgnoreCase("bank b"))   return new BankB();
        else if (bank.equalsIgnoreCase("bank c"))   return new BankC();

        return null;
    }

    @Override
    public Deposit getDeposit(String deposit)
    {
        return null;
    }
}
Deposit.java
public abstract class Deposit
{
    protected double rate;

    abstract void getInterestRate(double rate);

    public double calculateWithdrawalAmount(double amount, int tenure)
    {
        //W = P(1+r/100)^t
        return Math.floor(amount*(Math.pow((1+(rate/400)), (tenure*4))));
    }
}
DepositFactory.java
public class DepositFactory extends AbstractFactory
{

    @Override
    public Bank getBank(String bank)
    {
        return null;
    }

    @Override
    public Deposit getDeposit(String deposit)
    {
        if (deposit.equalsIgnoreCase("savings account"))        return new SavingsAccount();
        else if (deposit.equalsIgnoreCase("fixed deposit"))     return new FixedDeposit();
        else if (deposit.equalsIgnoreCase("recurring deposit")) return new ReccuringDeposit();

        return null;
    }
}
FactoryCreator.java
public class FactoryCreator
{
    public static AbstractFactory getFactory (String choice)
    {
        if (choice.equalsIgnoreCase("bank"))            return new BankFactory();
        else if (choice.equalsIgnoreCase("deposit"))    return new DepositFactory();

        return null;
    }    
}
FixedDeposit.java
public class FixedDeposit extends Deposit
{
    @Override
    void getInterestRate(double r)
    {
        rate = r;
    }
}
RecurringDeposit.java
public class ReccuringDeposit extends Deposit
{
    @Override
    void getInterestRate(double r)
    {
        rate = r;
    }
}
SavingsAccount.java
public class SavingsAccount extends Deposit
{
    @Override
    void getInterestRate(double r)
    {
        rate = r;
    }
}
UML Diagram of Factory Design Pattern

UML Diagram of Factory Design Pattern

The classes can be described as follows:

  1. AbstractFactory: This class provides an abstract factory class. Other factory classes can extend from this abstract class.
  2. ConcreteFactory: These are the concrete factory classes that extend from the AbstractFactory class.
  3. Object: This class is an abstract object class. Other objects can extend from this class.
  4. ConcreteObject: These classes are classes that extend from the Object abstract class.
  5. FactoryCreator: This class creates objects of type Factory and initializes them.
  6. DriverClass: This class contains the main() function and is responsible for handling the simulation of the program.
Pointe to Remember:
  1. Abstract Factory Pattern provides an interface for creating families of related dependent objects without specifying their concrete classes.
  2. The Abstract Factory Pattern provides a way to encapsulate a group of individual factories that have a common theme without specifying their concrete classes.
  3. The abstract factory design pattern is merely an extension of the factory pattern, which allows you to create objects without being concerned about the actual class of the object being created.
  4. Abstract means hiding some information and factory means which produces the products and pattern means a design. So, the Abstract Factory Pattern is a software design pattern that provides a way to encapsulate a group of individual factories that have a common theme.

In the next article, I am going to discuss Singleton Design Pattern in Java with Examples. Here, in this article, I try to explain the Abstract Factory Design Pattern in Java with Real-Time Examples. I hope this article will help you with your needs. I would like to have your feedback. Please post your feedback, question, or comments about this article.

1 thought on “Abstract Factory Design Pattern in Java”

  1. Hi,
    Your content is excellent, and I frequently refer to it due to its simplicity and clarity. However, I have a suggestion regarding your example of the abstract factory pattern in Java. In my opinion, the example related to the Bank Factory and Deposit Factory is not entirely correct. Here are a few points:

    It violates the Liskov substitution principle as it forces both concrete classes (Bank Factory and Deposit Factory) to implement both functions.
    AbstractFactory bankAbstractFactory = FactoryCreator.getFactory(“bank”);
    System.out.print(“Where would you like to deposit your money: “);
    Bank bank = bankAbstractFactory.getBank(br.readLine());

    In the above code, if the user inputs “deposit,” the code will break at “Bank bank = bankAbstractFactory.getBank(br.readLine());.”

    If we expect the client to be aware of the concrete class, the concept of abstraction is failing.

    Please review the example below to see if it can help correct the flow.

    public abstract class BankDeposits {
    public abstract Bank getBank();
    public abstract Deposit getDeposit();
    }

    public class PNBSavingDepositFactory extends BankDeposits {
    public Bank getBank() {
    return new PNBBank();
    }
    public Deposit getDeposit() {
    return new FixedDeposit();
    }
    }

    public class SBIRecurringDepositsFactory extends BankDeposits {
    public Bank getBank() {
    return new SBIBank();
    }
    public Deposit getDeposit() {
    return new RecurringDeposit();
    }
    }
    I hope this helps. Looking forward to your thoughts.
    Thanks
    Manoj Kumar

Leave a Reply

Your email address will not be published. Required fields are marked *