Abstract Factory is a creational design pattern that provides an interface for creating 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”.
Suppose we are managing the visual appearance of an application (say a text editor).
We declare an interface for each product: BgColor for the bg color and Text for the text. Then we declare subclasses for each family of product: LightText and LightBgcolor for the light theme and DarkText and DarkBgcolor for the dark theme.
Java implementation: BgColor interface and concrete products
interface BgColor {
// Operations for bgcolor
}
class LightBgColor implements BgColor {
// Operations for light-theme bgcolor
}
class DarkBgColor implements BgColor {
// Operations for dark-theme bgcolor
}
Java implementation: Text interface and concrete products
interface Text {
// Operations for text
}
class LightText implements Text {
// Operations for light-theme text
}
class DarkText implements Text {
// Operations for dark-theme text
}
Now we define code for the instantiation of each family of 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.
import java.util.Scanner;
class Client {
private BgColor bgColor;
private Text text;
public Client(String theme) {
if (theme.equals("Light")) {
bgColor = new LightBgColor();
text = new LightText();
}
else {
bgColor = new DarkBgColor();
text = new DarkText();
}
}
// Operations using bgColor and text
}
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter the theme - Light or Dark: ");
String inputTheme = scanner.nextLine();
Client client = new Client(inputTheme);
// Use the client object to perform operations using bgColor and text
}
}
There are some drawbacks to this approach:
To overcome these drawbacks, we use abstract factory design pattern.
We can solve the problem of hard-code instantiation of products using interfaces. For this, we declare interfaces for each product (BgColor and Text), and implement the concrete subclasses for specific themes (LightText, DarkText, LightBgColor, DarkBgColor).
We also create an abstract ThemeFactory class that declares an interface consisting of methods to instantiate each product family. Now, for each product family, we create concrete factories (LightFactory and DarkFactory) derived from the ThemeFactory.
The clients will call methods of the ThemeFactory to obtain product instances, but at the same time, they are unaware of the concrete classes they're using. So, clients remain independent of the current family of products.
The last step is to decide which variant of the product family will be used (in the main()). Accordingly, an object of that factory is passed to the client code, which then uses abstract product classes to create variant-specific concrete products!
Abstract and Concrete products of BgColor
abstract class BgColor {
public abstract String setBg();
}
class LightBgColor extends BgColor {
@Override
public String setBg() {
return "light.";
}
}
class DarkBgColor extends BgColor {
@Override
public String setBg() {
return "dark.";
}
}
Note: All products should be able to interact with each other! But we ensure interaction between products of the same family only for the sake of uniformity and flow in the application. For this, the abstract product class for Text will use a method for interaction between bg color and text products.
Abstract and concrete products of Text
abstract class Text {
public abstract String setText();
public abstract String interact(BgColor interactor);
}
class LightText extends Text {
@Override
public String setText() {
return "text-light";
}
@Override
public String interact(BgColor interactor) {
String result = interactor.setBg();
return "text-light interacted with " + result;
}
}
class DarkText extends Text {
@Override
public String setText() {
return "text-dark";
}
@Override
public String interact(BgColor interactor) {
String result = interactor.setBg();
return "text-dark interacted with " + result;
}
}
Abstract Factory (ThemeFactory) provides methods to create each distinct product and concrete factories (LightFactory and DarkFactory) implement these methods to produce appropriate products.
interface ThemeFactory {
BgColor createBg();
Text createText();
}
class LightFactory implements ThemeFactory {
@Override
public BgColor createBg() {
return new LightBgColor();
}
@Override
public Text createText() {
return new LightText();
}
}
class DarkFactory implements ThemeFactory {
@Override
public BgColor createBg() {
return new DarkBgColor();
}
@Override
public Text createText() {
return new DarkText();
}
}
The client uses only the ThemeFactory, BgColor, and Text to instantiate proper products.
class Client {
private ThemeFactory factory;
public Client(ThemeFactory factory) {
this.factory = factory;
}
public void run() {
BgColor bgColor = factory.createBg();
Text text = factory.createText();
System.out.println(bgColor.setBg() + " " + text.setText());
System.out.println(text.interact(bgColor));
}
}
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("Enter the theme - Light or Dark: ");
String inputTheme = scanner.nextLine();
ThemeFactory themeFactory;
if (inputTheme.equals("Light"))
themeFactory = new LightFactory();
else if (inputTheme.equals("Dark"))
themeFactory = new DarkFactory();
else {
System.out.println("ERROR");
return;
}
Client client = new Client(themeFactory);
client.run();
}
}
We should use the Abstract Factory when:
Some of the popular uses of this design pattern can be found in:
Abstract Factory is a creational design pattern that should be used when there are multiple families of products and all the instantiated products should belong to a single family. The responsibility of correctly instantiating the products is not the user's concern.
We also observed the issues caused by the naive approach of hard-coding the instantiation of various products based on their families, and we solved them by using the Abstract Factory pattern.
However, it is worth mentioning that adding new products can become challenging when following this design pattern due to the hierarchy of interfaces and concrete classes. Nonetheless, the Abstract Factory pattern offers several advantages in terms of code organization and flexibility.
Thanks to Ankit Nishad for his contribution in creating the first version of this content. If you have any queries or feedback, please write us at contact@enjoyalgorithms.com. Enjoy learning, Enjoy oops!
Subscribe to get well designed content on data structure and algorithms, machine learning, system design, object orientd programming and math.
©2023 Code Algorithms Pvt. Ltd.
All rights reserved.