Abstract Factory Design Pattern in Java
Introduction
The Abstract Factory Pattern is another powerful creational design pattern. While the Factory Method Pattern focuses on creating one product at a time, the Abstract Factory Pattern is about creating families of related objects without specifying their concrete classes.
In simpler words:
- A Factory Method creates one product.
- An Abstract Factory creates a set of products that are meant to be used together.
This pattern is especially useful when your system needs to be platform-independent or when it must support multiple product variants.
Problem Statement
Imagine you are designing a Cross-Platform UI Library that must support two environments:
- Windows
- Mac
Each environment has its own UI look and feel. For example:
- A Windows application has Windows-style Buttons and Windows-style Checkboxes.
- A Mac application has Mac-style Buttons and Mac-style Checkboxes.
Now, the problem:
- You need a way to create buttons and checkboxes, but ensure that they match the environment (Windows or Mac).
- If you mix them accidentally (e.g., Windows Button + Mac Checkbox), the UI will look inconsistent.
If you directly hardcode object creation like this:
if (os.equals("Windows")) {
Button btn = new WindowsButton();
Checkbox chk = new MacCheckbox(); // mistake: mixed!
}
…it becomes messy, error-prone, and violates loose coupling.
Solution with Abstract Factory Pattern
The Abstract Factory Pattern groups related factories into one “super factory” (an abstract factory).
- Define abstract products (e.g., Button, Checkbox).
- Define an abstract factory that declares creation methods for these products.
- Implement concrete factories for each environment (Windows, Mac).
- Clients only depend on the abstract factory, never on concrete classes.
This way, the client always gets a consistent family of products.
Implementation in Java
Step 1: Abstract Product Interfaces
// Product A
public interface Button {
void render();
}
// Product B
public interface Checkbox {
void render();
}
Step 2: Concrete Products (Windows Family)
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("Rendering a Windows-style button");
}
}
public class WindowsCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering a Windows-style checkbox");
}
}
Step 3: Concrete Products (Mac Family)
public class MacButton implements Button {
@Override
public void render() {
System.out.println("Rendering a Mac-style button");
}
}
public class MacCheckbox implements Checkbox {
@Override
public void render() {
System.out.println("Rendering a Mac-style checkbox");
}
}
Step 4: Abstract Factory
public interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
}
Step 5: Concrete Factories
public class WindowsFactory implements GUIFactory {
@Override
public Button createButton() {
return new WindowsButton();
}
@Override
public Checkbox createCheckbox() {
return new WindowsCheckbox();
}
}
public class MacFactory implements GUIFactory {
@Override
public Button createButton() {
return new MacButton();
}
@Override
public Checkbox createCheckbox() {
return new MacCheckbox();
}
}
Step 6: Client Code
public class Application {
private Button button;
private Checkbox checkbox;
public Application(GUIFactory factory) {
button = factory.createButton();
checkbox = factory.createCheckbox();
}
public void renderUI() {
button.render();
checkbox.render();
}
public static void main(String[] args) {
// Decide the environment at runtime
String os = "Mac"; // could come from config, system property, etc.
GUIFactory factory;
if (os.equalsIgnoreCase("Windows")) {
factory = new WindowsFactory();
} else {
factory = new MacFactory();
}
Application app = new Application(factory);
app.renderUI();
}
}
Output (if Mac is selected)
Rendering a Mac-style button
Rendering a Mac-style checkbox
Output (if Windows is selected)
Rendering a Windows-style button
Rendering a Windows-style checkbox
Notice how:
- The client (Application) doesn’t know or care about WindowsButton or MacCheckbox.
- The factory guarantees that both Button and Checkbox belong to the same family.
Real-World Analogy
Think of a furniture shop.
- You don’t want to mix a Victorian chair with a Modern sofa — they should match.
- A Victorian Furniture Factory creates Victorian-style products (chairs, sofas, tables).
- A Modern Furniture Factory creates modern-style products.
When you pick a style, the factory ensures consistency.
Advantages of Abstract Factory Pattern
- Ensures Consistency: Creates families of related objects that work well together.
- Loose Coupling: Client code depends only on interfaces, not concrete classes.
- Open/Closed Principle: Adding a new product family (LinuxFactory) doesn’t affect existing code.
- Runtime Flexibility: Choose product family at runtime (Windows, Mac).
Disadvantages
- Complexity: More classes and interfaces compared to Factory Method.
- Difficult to Extend Products Individually: Adding a new product type (e.g., Slider) requires changes in all factories.
When to Use
- When your system needs to be independent of product creation.
- When you want to enforce that a family of related products is used together.
- When supporting multiple product variants (themes, platforms, UI kits).
Conclusion
The Abstract Factory Pattern builds upon the Factory Method by dealing with groups of related objects. It ensures consistency across product families, making it ideal for cross-platform applications, theming engines, or plugin-based architectures.
In our example, we built a Cross-Platform UI Library where the client chooses between Windows and Mac factories, and gets a consistent look-and-feel.
Next, we will study the Singleton Design Pattern — a pattern that ensures a class has only one instance and provides a global point of access to it.