Abstract Factory Design Pattern

EnjoyAlgorithms Blog Cover Image

What is Abstract Factory design pattern?

Abstract Factory is a creational design pattern that helps in providing an interface for producing families of related objects (products) without specifying their concrete classes. In other words, it is a hierarchy of classes that encapsulates many possible “platforms” and the construction of a suite of “products” or “features”.

Let’s understand Abstract Factory via a problem!

Consider a temperature-sensitive machine whose features should vary with external temperature. Suppose these features or products are humidity and radiance. If the temperature is above 20 degrees celsius then humidity and radiance have some properties, and below that, some different properties.

As per the consideration, suppose we have two families of products, namely high_temp (temperature above 20 degrees Celsius) and low_temp (temperature below 20 degrees Celsius). So each product family consists of the same features but some differences in properties.

A basic approach would be to define a class for each product which takes a parameter as input to decide which variant of that product is to be instantiated. E.g., considering the humidity product:

//function pseudocode 
create_humidity (paramter)
{
    if (paramter == 1)
    {
        //create type 1 humidity
    }
    
    else if (paramter == 2)
    {
        //create type 2 humidity
    }
    .
    .
    .
}

There are some drawbacks to this approach:

  • Adding another type of humidity in the above code will break the Open-Closed principle, which implies, the code is not clean and hard to maintain in the future!
  • Also, the create_humidity method does not follow the Single Responsibility Principle as it is responsible for instantiating too many types of products.
  • One more possibility is that if the parameter is changed somehow, this code does not completely ensure that all products are of the same family.

To overcome these drawbacks, we use an abstract factory design pattern.

Brief-Solution

We can solve this problem of hard-code instantiation and creation of all products of the same family by using interfaces for each product and each variant.

  • We declare interfaces for each particular product of the general family (i.e., humidity and radiance), and the concrete subclasses implement products for specific temperature sets (highhumidity, highradiance, etc.).
  • Now, we will create an abstract SetFactory class that declares an interface consisting of methods for creating each product of the general family (a general family is the family of products independent of the variant, i.e., only the products).

  • Now, for each variant, we create a concrete factory derived from the SetFactory interface. Each of these concrete factories (HighSetFactory and LowSetFactory) will instantiate products of the respective variant.
  • The clients (more like a temperature sensor) call methods of SetFactory to obtain products instances, but at the same time, they are unaware of the concrete classes they’re using. Therefore, clients stay independent of the current temperature variant.
  • The last step is to decide which variant will be used (usually done in the main()). Accordingly, an object of that factory is passed to the client code, which then uses abstract product classes to produce variant-specific concrete products!

Takeaways

Using this approach of Abstract Factory pattern, we observe the following:

  1. Clients stay independent of the current variant, i.e., clients don’t need to worry about which variant of the product is to be instantiated as it is taken care of by the Client code.
  2. All the features belong to a single family at a time.
  3. Clients only have to interact with the interface (SetFactory) and not with a particular concrete class. Therefore, it is easy to maintain or update concrete classes without hampering user experience.
  4. Exchanging families is easy (think about the last step of a quick solution).
  5. The client is less coupled to concrete classes as it interacts with interfaces.
  6. This approach follows both the principles- Open-Closed principle and SRP. As we can add another variant factory in the future. Each class is responsible for only one type of function (either provides an interface or implements the interface).

Components and Structure

In the UML diagram:

  • Solid arrows depict entities that the Client uses.
  • Dashed lines depict inheritance.
  • The solid line for family1 and the dotted line for family2.
  • Abstract Products (humidity, radiance)
    Provide interfaces for each specific product of the general family.
  • Concrete Products (highhumidity, lowhumidity, highradiance, lowradiance)
    Sub-classes of appropriate abstract products provide implementation details of variant-specific (family-specific) products.
  • Abstract Factory (SetFactory)
    An interface that declares methods for the creation of each abstract product.
  • Concrete Factories (HighSetFactory, LowSetFactory)
    Sub-classes of abstract factory implement the creation methods of all the family-specific products via their concrete classes.
  • Client (Client)
    It takes an abstract factory object and hence instantiates corresponding product families. It only interacts via the interfaces and not through concrete classes.

NOTE: Although the methods of concrete factories instantiate concrete products, the return type of each method is set as the corresponding abstract product.

Implementation with Example

Let’s say you are managing the visual appearance of an application (say a text editor).

  • This editor has two key features- bgcolour and text. Therefore, a family of products consists of bgcolour + text.
  • Moreover, you provide two themes of the editor: light and dark. The features have different appearances and properties in each theme.
  • Now, you have two families of products/features in the app, i.e., Light theme-based bgcolour + text.
    Dark theme-based bgcolour + text.

Task

You want to add these two themes into your editor such that:

  • all the products should be consistent with the chosen theme.
  • Coupling between client code and concrete classes of the products should be minimal!

Solution Approach

We have already discussed the in-efficient approach in the example of a temperature-sensitive machine. So let’s go straight into the Abstract factory pattern approach.

UML for ThemeFactory

We create abstract product classes — BGCOLOUR and TEXT. Then, we implement methods of these abstract products in each theme-based concrete products class, e.g., lightbgcolour, darkbgcolour, light_text, and dark_text.

We create the Abstract Factory — ThemeFactory, which declares methods for creating all abstract products. Now, for each theme, we create a concrete factory derived from the ThemeFactory interface. Each of these concrete factories (DarkFactory and LightFactory) returns products of a particular theme. A crucial realization is that although these are concrete classes, the return type in the signature of the methods should return abstract product types (BGCOLOUR and TEXT) instead of concrete ones. By doing so, the part of code that uses factory objects (the Client code) doesn’t get coupled to a specific variant of products.

Then we add client code that will work with factories and products via their interfaces. 
For more clarity, compare these classes with those in the UML diagram shown earlier.

Further, based on the current theme, the suitable object of the factory is passed to the client code. The entire client code uses this factory object to create an appropriate family of products. Moreover, whenever the theme needs to be changed, we pass the other factory object, repeating the same process.

Solution Code C++

Abstract product class for bgcolour

class BGCOLOUR 
{
    public:
    virtual string set_bg() = 0;
};

Concrete products of bgcolour for each variant using BGCOLOUR

class light_bgcolour : public BGCOLOUR 
{
    public:
    string set_bg() override 
    {
        return "light.";
    }
};

class dark_bgcolour : public BGCOLOUR 
{
    public:
    string set_bg() override 
    {
        return "dark.";
    }
};

Note: All products should be able to interact with each other! But we must ensure interaction between products of the same family only for the sake of uniformity and flow in the application.

Abstract product class for text consists of an additional method for interaction between bgcolour and text products.

class TEXT 
{
    public:
    virtual string set_txt() = 0;
    virtual string interact(BGCOLOUR &interactor) = 0;
    //By using Abstract factory, we make sure that both products are of //the same family
};

concrete products of text for each variant using TEXT

class light_text : public TEXT 
{
    public:
    string set_txt() override 
    {
        return "text - light";
    }
    
    string interact(BGCOLOUR &interactor) override 
    {
        string result = interactor.set_bg();
        return ("text-light interacted with "+ result);
    }
};


class dark_text : public TEXT 
{
    public:
    string set_txt() override 
    {
        return "text-dark";
    }
  
    string interact(BGCOLOUR &interactor) override 
    {
        string result = interactor.set_bg();
        return ("text-dark interacted with "+ result);
    }
};

Abstract Factory, provides methods to create each distinct product

class ThemeFactory 
{
    public:
    virtual BGCOLOUR *create_bg() = 0;
    virtual TEXT *create_txt() = 0;
};

Concrete factories, derived from Abstract factory implement methods of Abstract factory to produce appropriate products (Concrete factories for light and dark theme).

class LightFactory : public ThemeFactory 
{
    public:
    BGCOLOUR *create_bg() override 
    {
        return new light_bgcolour();
    }
    
    TEXT *create_txt() override 
    {
        return new light_text();
    }
};

class DarkFactory : public ThemeFactory 
{
    public:
    BGCOLOUR *create_bg() override 
    {
        return new dark_bgcolour();
    }
    
    TEXT *create_txt() override 
    {
        return new dark_text();
    }
};

The client uses only the abstract classes ThemeFactory, BGCOLOUR, and TEXT to instantiate proper products.

void Client(ThemeFactory &factory) 
{
    BGCOLOUR *bgcolour = factory.create_bg();
    TEXT *text = factory.create_txt();
    cout << bgcolour->set_bg() << " " << text->set_txt() << "\n";
    cout << text->interact(*bgcolour) << "\n";
}

int main() 
{
    string input_theme; 
    cout << "Enter the theme- Light or Dark : \n";
    cin >> input_theme;
    ThemeFactory *in;
    
    if (input_theme == "Light")
        in = new LightFactory;
    else if (input_theme == "Dark")
        in = new DarkFactory;
    else 
    {
        cout << "ERROR\n";
        exit(0};
    } 
    //pasing current theme to Client for instantiation
    Client (*in);
    //changing factory type of "in", we can change the family
    //without going into concrete classes
}

Solution Code Java

import java.util.*;

public class java_abstract 
{
    //product interface for bgcolour    
    public static interface BGCOLOUR 
    {
        String set_bg();
    }
    
    //concrete products
    public static class light_bgcolour implements BGCOLOUR 
    {
        @Override
        public String set_bg() 
        {
            return ("light.");
        }
    }

    public static class dark_bgcolour implements BGCOLOUR 
    {
        @Override
        public String set_bg() 
        {
            return ("dark.");
        }
    }

    //product interface for text 
    public static interface TEXT 
    {
        String set_txt();
        String interact(BGCOLOUR interactor);
    }
    
    //concrete products
    public static class light_txt implements TEXT 
    {
        @Override
        public String set_txt() 
        {
            return ("text-light");
        }
    
        @Override
        public String interact (BGCOLOUR interactor) 
        {
            String res = interactor.set_bg();
            return ("text-light interacted with "+ res);
        }
    }

    public static class dark_txt implements TEXT 
    {
        @Override
        public String set_txt() 
        {
            return("text-dark");
        }
        
        @Override
        public String interact (BGCOLOUR interactor) 
        {
            String res = interactor.set_bg();
            return ("text-dark interacted with "+ res);
        }
    }
    
    //Abstract Factory - ThemeFactory
    public static interface ThemeFactory 
    {
        BGCOLOUR create_bg();
        TEXT create_txt();
    }
    
    //concrete factories
    public static class LightFactory implements ThemeFactory 
    {
        @Override
        public BGCOLOUR create_bg() 
        {
            return new light_bgcolour();
        }
        
        @Override
        public TEXT create_txt() 
        {
            return new light_txt();
        }
    }
    
    public static class DarkFactory implements ThemeFactory 
    {
        @Override
        public BGCOLOUR create_bg() 
        {
            return new dark_bgcolour();
        }
        
        @Override
        public TEXT create_txt() 
        {
            return new dark_txt();
        }
    }

    //Client code for instantiation, makes sure all products are compatible
    public static void Client (ThemeFactory factory)
    {
        BGCOLOUR bgcolour = factory.create_bg();
        TEXT text = factory.create_txt();
        
        System.out.println (bgcolour.set_bg() + " " + text.set_txt());
        System.out.println ('\n' + text.interact(bgcolour)+'\n');
    }
    
    //main call
    public static void main(String[] args) 
    {
        Scanner sc= new Scanner(System.in); 
        System.out.println("Enter the theme- Light or Dark : \n");  
        String input_theme = sc.nextLine();
        ThemeFactory in;
        
        if(input_theme.equals("Light"))
        {
            in = new LightFactory();
            //instantiation light theme
            Client(in);
        }
        else if (input_theme.equals("Dark"))
        {
            in = new DarkFactory();
            //instantiaon dark theme
            Client(in);
        }
        else
        {
            System.out.println ("ERROR! \n");
            System.exit(0);
        }
    }
}

When to apply the idea of Abstract Factory?

We should use the Abstract Factory pattern when

  • The type of products to be created is not known a priori.
  • A system should be independent of how its products are created, maintained, and represented.
  • A system is constrained to use a particular family of related products among various such families, and the user should not be bothered about it.
  • A system should instantiate its products via interfaces only, and their implementations are not revealed.
  • A system should be easily extensible (in terms of new families) without coupling the code to concrete classes.

Advantages of Abstract Factory

  • It follows the Open-Closed principle as we can add another variant factory in the future without altering the existing code. (Whereas the conventional if-else-if method was lacking this)
  • It follows the Single Responsibility principle as we divide the responsibilities into different concrete classes and interfaces. (Lacked in conventional if-else-if method)
  • Isolates the users from implementations of various products as users interact with interfaces only.
  • Uniformity of a single-family is maintained throughout the program, i.e., maintains consistency among products.
  • As already seen, this pattern makes changing product families very easy.

Disadvantages of Abstract Factory

  • Many interfaces and their sub-classes make the code complex and large.
  • Adding new products is difficult. Because the Abstract Factory fixes the products. To add new products, we have to change Abstract Factory and all the factories it follows.

Applications of Abstract Factory

Some of the popular uses of this design pattern can be found in

  • In many Java libraries, e.g., in XPathFactory, the newInstance() method returns a factory object of XPathFactory, used to create XPath objects.
  • Another such Java library: TransformerFactory. (Working is analogous to XPathFactory)
  • ET++ (object-oriented application framework implemented in C++) uses this design pattern for portability across different window systems.
  • InterViews (a C++ graphical interface toolkit) denotes AbstractFactory classes with a “Kit” suffix such as DialogKit and WidgetKit.

Conclusion

Abstract Factory pattern is a creational design pattern that should be used when various families of more than one product; all the instantiated products should belong to a single-family. The responsibility of correct instantiation of product is not the user’s concern. Moreover, it is in line with the Open-Closed, Single responsibility principles. We also saw the issues caused by the naive hard-coded family-based instantiation of various products and solved them using the Abstract Factory pattern. Then, we saw the scenarios where it is applicable, e.g., when a system should be independent of how the products are created, when a system is prone to add new families in the future, etc. Also, adding new products will be difficult if we follow this design pattern because of the hierarchy of the interfaces and concrete class. Then we saw some uses of this design pattern!

Enjoy learning, Enjoy design patterns!

We'd love to hear from you

More content from EnjoyAlgorithms

Our weekly newsletter

Subscribe to get free weekly content on data structure and algorithms, machine learning, system design, oops and math. enjoy learning!