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”.
Suppose we are managing the visual appearance of an application (say a text editor).
We declare a base class for each feature, like BgColor for the bgcolour feature and Text for the text feature. Then we declare subclasses for each theme-based product. For e.g., LightText and LightBgcolour for light theme products and DarkText and DarkBgcolour for dark theme products.
C++ implementation: BgColor base class and concrete products
class BgColor
{
//Operations for bgcolour
};
class LightBgcolour : public BgColor
{
//Operations for light-theme bgcolor
};
class DarkBgcolour : public BgColor
{
//Operations for dark-theme bgcolor
};
C++ implementation: Text base class and concrete products
class Text
{
//Operations for text
};
class LightText : public Text
{
//Operations for light-theme text
};
class DarkText : public Text
{
//Operations for dark-theme text
};
Now we define code for the instantiation of theme-based concrete products inside the Client class. To do this, we define a constructor inside the Client class, which is parameterized on the basis of the theme. For the light theme, we instantiate light theme-based products, and likewise for the dark theme.
class Client
{
private:
BgColour *bgcolor;
Text *text;
public:
//parameterized constructor
Client(string theme)
{
if(theme == "Light")
{
bgcolour = new LightBgcolour;
text = new LightText;
}
else
{
bgcolour = new DarkBgcolour;
text = new DarkText;
}
}
//Operations using bgcolour and text
};
int main()
{
string input_theme;
cout << "Enter the theme- Light or Dark : \n";
cin >> input_theme;
Client client(input_theme);
}
There are some drawbacks to this approach:
To overcome these drawbacks, we use an abstract factory design pattern.
Solution Idea
We can solve this problem of hard-code instantiation and the creation of products using interfaces for each product family and each product feature. For this, we declare interfaces for each product feature of the (i.e., bgcolour and text), and the concrete subclasses implement product features for specific themes (lighttext, lightbgcolour, etc.).
We also create an abstract ThemeFactory class that declares an interface consisting of methods to instantiate and create each product family. Now, for each product, we create concrete factories (LightFactory and DarkFactory) derived from the ThemeFactory interface.
The clients call methods of ThemeFactory to obtain product instances, but at the same time, they are unaware of the concrete classes they’re using. Therefore, clients stay independent of the current family of products.
The last step is to decide which variant of the product family 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!
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
}
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);
}
}
}
Using this approach, we can observe the following:
In the UML diagram:
NOTE: Although the methods of concrete factories instantiate concrete products, the return type of each method is set as the corresponding abstract product.
We should use the Abstract Factory pattern when
Some of the popular uses of this design pattern can be found in
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!