June 17, 2026 in patterns5 minutes
A intermediate-level guide to Abstract Factory: before-and-after java code and diagrams for a CS student.
Imagine you are building a UI library that supports different visual themes. A “Light Mode” might use soft blue buttons and grey scrollbars, while a “Dark Mode” uses charcoal buttons and dark-grey scrollbars. To keep the application looking professional, these components must always match; there is nothing worse than a bright white button sitting in the middle of a pitch-black window.
As you add more components—checkboxes, sliders, text fields—your instantiation logic starts to rot. You find yourself writing repetitive conditional logic every time you need a new piece of the UI. This “Switch Statements” smell occurs when you use if/else or switch blocks to decide which concrete class to instantiate based on a type or a configuration string.
class UIComponent {}
class LightButton extends UIComponent {}
class DarkButton extends UIComponent {}
class LightScrollbar extends UIComponent {}
class DarkScrollbar extends UIComponent {}
class UserInterface {
private String theme;
public UserInterface(String theme) {
this.theme = theme;
}
// Problem: Constructor bloat and conditional logic everywhere
// Risk of mixing LightButton with DarkScrollbar due to manual selection
public UIComponent createButton() {
if (theme.equals("LIGHT")) {
return new LightButton();
} else if (theme.equals("DARK")) {
return new DarkButton();
}
throw new IllegalArgumentException("Unknown theme");
}
public UIComponent createScrollbar() {
if (theme.equals("LIGHT")) {
return new LightScrollbar();
} else if (theme.equals("DARK")) {
return new DarkScrollbar();
}
throw new IllegalArgumentException("Unknown theme");
}
}The problem here is two-fold: first, the UserInterface class has grown into a bloated mess of conditional logic. Second, and more dangerously, it creates an opportunity for “mismatched families.” Because each method (createButton, createScrollbar) makes its own independent decision about which theme to use, a small logic error could result in a Light Button being paired with a Dark Scrollbar.
classDiagram
direction BT
UIComponent <|.. LightButton
UIComponent <|.. DarkButton
UIComponent <|.. LightScrollbar
UIComponent <|.. DarkScrollbar
UserInterface ..> UIComponent : creates
note for UserInterface "Contains repetitive if/else \nlogic for every component"
To fix this, we apply the Replace Conditional with Polymorphism refactoring at a structural level. We introduce the Abstract Factory, a creational pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes.
Instead of asking “What theme is active?” every time we need a button, we ask a specific factory to handle it. If we have a LightThemeFactory, it knows how to produce the entire suite of light-themed components correctly and consistently.
interface UIComponent {}
class LightButton implements UIComponent {}
class DarkButton implements UIComponent {}
class LightScrollbar implements UIComponent {}
class DarkScrollbar implements UIComponent {}
// The Abstract Factory interface
interface UIFactory {
UIComponent createButton();
UIComponent createScrollbar();
}
class LightThemeFactory implements UIFactory {
public UIComponent createButton() { return new LightButton(); }
public UIComponent createScrollbar() { return new LightScrollbar(); }
}
class DarkThemeFactory implements UIFactory {
public UIComponent createButton() { return new DarkButton(); }
public UIComponent createScrollbar() { return new DarkScrollbar(); }
}
class UserInterface {
private final UIFactory factory;
// Client code is decoupled from concrete implementations
public UserInterface(UIFactory factory) {
this.factory = factory;
}
public UIComponent createButton() { return factory.createButton(); }
public UIComponent createScrollbar() { return factory.createScrollbar(); }
}In this refactored version, the client (UserInterface) no longer cares about “Light” or “Dark.” It only knows that it has an object implementing the UIFactory interface. This achieves high cohesion: the logic for what to create is moved out of the UI logic and into dedicated factory objects.
classDiagram
direction BT
UIFactory <|.. LightThemeFactory
UIFactory <|.. DarkThemeFactory
UIComponent <|-- LightButton : implements
UIComponent <|-- DarkButton : implements
UIComponent <|-- LightScrollbar : implements
UIComponent <|-- DarkScrollbar : implements
LightThemeFactory ..> LightButton : creates
LightThemeFactory ..> LightScrollbar : creates
DarkThemeFactory ..> DarkButton : creates
DarkThemeFactory ..> DarkScrollbar : creates
UserInterface o-- UIFactory : uses
When the application runs, the interaction follows a clear delegation flow. The client asks the factory for a component, and the factory handles the messy details of instantiation.
sequenceDiagram
participant UI as UserInterface
participant F as LightThemeFactory
participant B as LightButton
UI ->> F: createButton()
F ->> B: new LightButton()
B -->> F: return instance
F -->> UI: return instance
It is common to confuse these two, but the distinction lies in the “scale” of creation.
The Abstract Factory pattern is a powerful tool for maintaining consistency in complex systems, but it comes with architectural costs.
Use it when:
Do not use it when:
ColorPicker) requires updating the Abstract Factory interface and every concrete factory implementation, which can lead to “Shotgun Surgery.”Using the Abstract Factory to ensure a consistent family of products
UserInterface ui = new UserInterface(new DarkThemeFactory());
UIComponent btn = ui.createButton(); // Guaranteed to be DarkButton