Abstract Factory Design Pattern

In Abstract Factory Design Pattern provides an interface or abstract class for creating a family of related or dependent objects, without explicitly specifying their concrete classes. It comes under creational pattern as it abstract the complex logic of creating various related object from client. It allows a client to use an interface to create a set of related objects without knowing about the concrete classes that are actually produced.

It is an extension of the Factory Design Pattern, aiming to encapsulate a group of individual factories that share a common theme. In this tutorial, we will delve into the concepts of the Abstract Factory Design Pattern, its structure, benefits, and how to implement it in Java.


Structure of the Abstract Factory Design Pattern

The Abstract Factory Design Pattern involves the following components:
  • Abstract Factory : This serves as an interface or abstract class, outlining a series of abstract methods, each tasked with creating a set of interrelated products.

  • Concrete Factory : These are classes that implement the abstract factory interface or extend the abstract factory class. Each concrete factory takes on the responsibility of crafting a distinct family of products.

  • Abstract Product : This is either an interface or abstract class that defines the blueprint for a product type.

  • Concrete Product : These are classes that implement the abstract product interface or extend the abstract product class. Each concrete product belongs to a specific family of products.

  • Client : The client code interacts with the abstract factory and abstract product interfaces or classes, creating families of related objects without knowing their concrete implementations. This interaction results in the generation of families of interconnected objects, with the client remaining oblivious to their tangible implementations.

Advantages of Abstract Factory Pattern

The Hidden Crafting Technique, commonly known as the Abstract Factory Pattern, offers several advantages that contribute to the development of modular and adaptable software systems:
  • Encapsulation of Product Creation : The abstract factory conceals the intricacies of product creation, enhancing modularity. This encapsulation fosters an environment where the addition or alteration of product families is a straightforward process.

  • Promotion of Consistent Design : Embracing a systematic approach, the pattern promotes a uniform design by structuring the creation of related objects. This organized methodology contributes to a codebase that is both comprehensible and easily maintainable.

  • Scalability : Abstract factories gracefully accommodate the expansion of product families without necessitating modifications to existing code. This inherent scalability makes the pattern well-suited for projects that undergo evolutionary changes.

  • Consistency Across Product Families : The pattern ensures that products created by a concrete factory are compatible and consistent within a family. This feature proves invaluable in scenarios where diverse products must seamlessly interact.

  • Isolation of Concrete Classes : Clients using the abstract factory interface or class are shielded from the details of product creation. Interactions solely involve abstract types, facilitating the substitution of product families without imposing alterations on client code.

When we should use Abstract Factory Pattern

  • When we want to abstract the logic of object creation and want system to be independent of ways its objects are created.
  • When a system needs to create a similar or related set of objects.
  • When you want to expose an abstract library for creating families of similar objects through interfaces and by hiding implementation details.

Implementation of Abstract Factory Design Pattern

Abstract Factory Design Pattern UML Diagram

We are going to create a 'Bird' and 'Animal' interfaces and concrete classes of various birds and animals implementing these interfaces.

Bird.java
public interface Bird {
    void fly();
}
Eagle.java
public class Eagle implements Bird {

   @Override
   public void fly() {
      System.out.println("Eagle is Flying");
   }
}
Sparrow.java
public class Sparrow implements Bird {

   @Override
   public void fly() {
      System.out.println("Sparrow is Flying");
   }
}

Animal.java
public interface Animal {
    void run();
}
Lion .java
public class Lion implements Animal {

    @Override
    public void run() {
        System.out.println("Lion is Running");
    }
}
Horse.java
public class Horse implements Animal {

    @Override
    public void run() {
        System.out.println("Horse is Running");
    }
}

Next, we create an abstract factory class AbstractFactory which is extended by 'BirdFactory' and 'AnimalFactory' factory classes.

AbstractFactory.java
public abstract class AbstractFactory {
    abstract Bird getBird(String birdType);
    abstract Animal getAnimal(String animalType) ;
}
AnimalFactory.java
public class AnimalFactory extends AbstractFactory {
 
    @Override
    public Animal getAnimal(String animalType){
        if(animalType == null){
            return null;
        }  
       
        if(animalType.equalsIgnoreCase("LION")){
            return new Lion();
        } else if (animalType.equalsIgnoreCase("HORSE")){
            return new Horse();
        } else {
            return null;
        }
    }
    
    @Override
    Bird getBird(String birdType) {
        return null;
    }
}
BirdFactory.java
public class BirdFactory extends AbstractFactory {
 
    @Override
    public Bird getBird(String birdType){
     if(birdType == null){
         return null;
     }  
       
        if(birdType.equalsIgnoreCase("EAGLE")){
          return new Eagle();
     } else if (birdType.equalsIgnoreCase("SPARROW")){
          return new Sparrow();
     } else {
          return null;
     }
    }
    
    @Override
    Animal getAnimal(String animalType) {
        return null;
 }
}

Then we will create a 'FactoryCreator' class to get appropriate AbstractFactory instances as per the passed input parameter. Finally we will create 'AbstractFactoryPatternSample' which uses and instance of FactoryProducer to get concrete implementation of AbstractFactory and creates various animal and bird objects.

public class FactoryCreator {
   public static AbstractFactory getFactory(String factoryType){

    /* It is a factory of factories */
      if(factoryType.equalsIgnoreCase("BIRD")){
         return new BirdFactory();         
      } else if (factoryType.equalsIgnoreCase("ANIMAL")){
         return new AnimalFactory();
      } else {
       return null;
      }
      
   }
}
public class AbstractFactoryPatternSample {
    public static void main(String[] args) {

        //get factory of Animals using FactoryCreator 
        AbstractFactory animalFactory = FactoryCreator.getFactory("ANIMAL");

        // create animal objects using AbstractFactory interface
        Animal lion = animalFactory.getAnimal("LION");
        Animal horse = animalFactory.getAnimal("HORSE");

     //get factory of Birds using FactoryCreator 
        AbstractFactory birdFactory = FactoryCreator.getFactory("BIRD");

        // create bird objects using AbstractFactory interface
        Bird sparrow = birdFactory.getBird("SPARROW");
        Bird eagle = birdFactory.getBird("EAGLE");
       
        // Call run method of Animal interface 
        lion.run();
        horse.run();
       
        // Call fly method of Bird interface 
        sparrow.fly();
        eagle.fly();
    }
}

Output

Lion is Running
Horse is Running
Sparrow is Flying
Eagle is Flying

Important Points about Abstract Factory Pattern
  • The intent of Abstract factory design pattern is to create families of related objects without having to depend on their concrete sub-classes.
  • The client doesn't know what factory it's going to use. First, it gets a Factory and then it calls factory method to get concrete classes.
  • It is a factory of factories.

Best Practices of Abstract Factory Design Pattern

  • Encapsulation of Object Creation : The entire process of generating families of interconnected objects should be encapsulated within the abstract factory. Encapsulation serves the purpose of preserving a distinct separation of concerns and guarantees that the client code's attention remains on utilizing the generated objects

  • Clear Separation of Concerns : It is essential to uphold a distinct segregation of concerns when it comes to the relationship between the abstract factories and the client code. It is ideal that clients should be able to concentrate on utilizing the abstract interfaces without being entangled in the intricacies of object construction or product families.

  • Consideration for Object Creation Logic : A consideration for object creation logic is that it should be well-encapsulated and adhere to established best practices for object creation when implemented within concrete factories. Avoid implementing intricate logic in the factory methods; if required, contemplate the use of parameterized factory methods.

  • Scalability and Extensibility :Develop abstract factories and products that possess the qualities of scalability and extensibility. Ensure that the introduction of variations or the addition of new product families does not necessitate changes to the existing code. The objective is to mitigate the effects of modifications on the current codebase.

  • Consistency Across Product Families :Ensure uniformity among product families produced by distinct concrete manufacturing facilities. It is ideal for products belonging to the same family to be compatible and operate in unison. This uniformity guarantees that customers can depend on the compatibility of the products.

  • Clear Abstractions :It is crucial to guarantee that the abstractions of your product and factory interfaces or classes are unambiguous and significant. Abstract factories ought to symbolize product families that are interconnected, while abstract products ought to establish the shared interface that concrete products within a family utilize.

  • Consistent Naming Conventions : Use clear and consistent naming conventions for interfaces, abstract classes, concrete classes, methods, and variables. This enhances code readability and makes it easier for other developers to understand the purpose of each component.

Related Topics
Factory Design Pattern
Singleton Design Pattern
Decorator Design Pattern
Facade Design Pattern
Flyweight Design Pattern
Interpreter Design Pattern