The Abstract Factory is a creational design pattern that allows you to produce families of related objects without specifying their concrete classes.
While the Factory Method handles creating one type of product (e.g., a Document), the Abstract Factory handles a suite of products that belong together (e.g., a Windows-style button and a Windows-style checkbox).
The Problem
Imagine you are building a UI Toolkit for your website's dashboard. You want it to support multiple themes: Light Mode and Dark Mode.
- In Light Mode, you need light buttons and light sidebars.
- In Dark Mode, you need dark buttons and dark sidebars.
If you mix a "Light Button" with a "Dark Sidebar," your UI looks broken. You need a way to ensure that all elements created are always from the same theme.
Step-by-Step Java Implementation
1. The Abstract Products
We define interfaces for the distinct products that make up our "family."
// Button interface
interface Button {
void render();
}
// Sidebar interface
interface Sidebar {
void show();
}
2. The Abstract Factory
This interface declares a set of methods for creating each of the abstract products.
interface ThemeFactory {
Button createButton();
Sidebar createSidebar();
}
3. Concrete Products (Light Theme)
Implementation of the products for the Light Theme family.
class LightButton implements Button {
public void render() { System.out.println("Rendering a bright white button."); }
}
class LightSidebar implements Sidebar {
public void show() { System.out.println("Showing a light-grey sidebar."); }
}
4. Concrete Products (Dark Theme)
Implementation of the products for the Dark Theme family.
class DarkButton implements Button {
public void render() { System.out.println("Rendering a sleek charcoal button."); }
}
class DarkSidebar implements Sidebar {
public void show() { System.out.println("Showing a deep black sidebar."); }
}
5. Concrete Factories
Each concrete factory corresponds to a specific variant of the products.
class LightThemeFactory implements ThemeFactory {
public Button createButton() { return new LightButton(); }
public Sidebar createSidebar() { return new LightSidebar(); }
}
class DarkThemeFactory implements ThemeFactory {
public Button createButton() { return new DarkButton(); }
public Sidebar createSidebar() { return new DarkSidebar(); }
}
Full Code for Testing
You can copy this entire block into a file named AbstractFactoryTest.java to run it.
// --- Product Interfaces ---
interface Button { void render(); }
interface Sidebar { void show(); }
// --- Concrete Products: Light Family ---
class LightButton implements Button {
public void render() { System.out.println("[UI] Rendering Light Button"); }
}
class LightSidebar implements Sidebar {
public void show() { System.out.println("[UI] Showing Light Sidebar"); }
}
// --- Concrete Products: Dark Family ---
class DarkButton implements Button {
public void render() { System.out.println("[UI] Rendering Dark Button"); }
}
class DarkSidebar implements Sidebar {
public void show() { System.out.println("[UI] Showing Dark Sidebar"); }
}
// --- The Abstract Factory ---
interface ThemeFactory {
Button createButton();
Sidebar createSidebar();
}
// --- Concrete Factories ---
class LightThemeFactory implements ThemeFactory {
public Button createButton() { return new LightButton(); }
public Sidebar createSidebar() { return new LightSidebar(); }
}
class DarkThemeFactory implements ThemeFactory {
public Button createButton() { return new DarkButton(); }
public Sidebar createSidebar() { return new DarkSidebar(); }
}
// --- Client Code ---
class DashboardApp {
private Button button;
private Sidebar sidebar;
public DashboardApp(ThemeFactory factory) {
// The App doesn't know which theme it's using!
button = factory.createButton();
sidebar = factory.createSidebar();
}
public void renderUI() {
button.render();
sidebar.show();
}
}
// --- Main Testing Class ---
public class AbstractFactoryTest {
public static void main(String[] args) {
System.out.println("--- Initializing Dashboard ---\n");
ThemeFactory factory;
String config = "DARK"; // This could come from user settings
if (config.equalsIgnoreCase("LIGHT")) {
factory = new LightThemeFactory();
} else {
factory = new DarkThemeFactory();
}
DashboardApp app = new DashboardApp(factory);
app.renderUI();
System.out.println("\n--- Theme Applied Successfully ---");
}
}
Why use Abstract Factory over Factory Method?
- Factory Method: Focuses on one product. Use it when you want to choose between different versions of a single object (e.g., PDF vs. TXT).
- Abstract Factory: Focuses on families of products. Use it when you need several related objects to work together and want to ensure they are compatible (e.g., Buttons, Sidebars, and Menus all being "Dark Mode").