Loading content...
Design patterns are more than just code templates—they're architectural vocabularies with precisely defined roles and collaborations. Understanding the participants in the Abstract Factory pattern enables you to:
In this page, we dissect the Abstract Factory pattern to its fundamental components, examining each participant's role, responsibility, and relationship to others.
By the end of this page, you will be able to identify and name all participants in the Abstract Factory pattern, understand their individual responsibilities, trace object interactions through sequence diagrams, and recognize the pattern structure in production code.
The Abstract Factory pattern involves five categories of participants, each with a distinct role in the overall collaboration:
| Participant | Role | Quantity | Example |
|---|---|---|---|
| AbstractFactory | Declares interface for creating product family | 1 interface | GUIFactory |
| ConcreteFactory | Implements creation for a specific family | 1 per family | WindowsFactory, MacOSFactory |
| AbstractProduct | Declares interface for a product type | 1 per product type | Button, TextField, Menu |
| ConcreteProduct | Implements a product for a specific family | Families × Products | WindowsButton, MacOSTextField |
| Client | Uses factories and products via abstractions | Many clients | Application, FormBuilder |
The Mathematics of Participants
For a system with F families and P product types:
For our UI example with 3 families and 6 product types:
The following UML class diagram illustrates the structural relationships between all participants in the Abstract Factory pattern:
Dashed arrows (..>) indicate "creates" relationships — a factory creates products. Solid arrows (-->) indicate "uses" relationships — the client uses the factory and products. Triangular arrows (<|..) indicate implementation — concrete classes implement interfaces.
The AbstractFactory participant defines the interface for creating a family of products. It is the central abstraction that enables polymorphic factory behavior.
createButton(), createTextField())1234567891011121314151617181920212223242526272829303132333435363738394041424344
/** * AbstractFactory: Defines the creation contract for product families. * * DESIGN DECISIONS: * * 1. Interface vs Abstract Class: * - Use interface when factories have no shared implementation * - Use abstract class when factories share common behavior * * 2. Return types are ALWAYS abstract product types: * - Button, not WindowsButton * - This enforces client abstraction * * 3. Method naming convention: "create" + ProductType * - Clearly communicates object creation * - Consistent across all factory patterns * * 4. No state by default: * - The interface declares only creation methods * - Concrete factories may hold configuration state */public interface GUIFactory { // Each method returns an ABSTRACT product type Button createButton(); TextField createTextField(); Checkbox createCheckbox(); Menu createMenu(); Dialog createDialog(); // Optional: Factory method for creating configured panels // This shows how factories can create composite products default Panel createLoginPanel() { Button submit = createButton(); TextField username = createTextField(); TextField password = createTextField(); submit.setText("Login"); username.setPlaceholder("Username"); password.setPlaceholder("Password"); return new Panel(submit, username, password); }}Use an interface when concrete factories share no implementation. Use an abstract class when factories share common behavior (like the createLoginPanel example) or common configuration state. Modern languages support default methods in interfaces, blurring this distinction.
ConcreteFactory participants implement the AbstractFactory interface for a specific product family. They encapsulate all family-specific knowledge and configuration.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
/** * ConcreteFactory: Creates products for the Windows family. * * KEY CHARACTERISTICS: * * 1. Knows about ONE family only (Windows) * - Imports only Windows-specific classes * - Knows nothing about macOS or Linux * * 2. May hold family-specific configuration * - Theme settings, DPI scaling, etc. * - Detected at construction time * * 3. Each creation method returns the interface type * - Returns Button, not WindowsButton * - Internal type is WindowsButton, but signature hides this */public class WindowsFactory implements GUIFactory { // Family-specific configuration (detected at construction) private final WindowsTheme theme; private final DpiScale dpiScale; private final AccessibilitySettings a11y; public WindowsFactory() { this.theme = WindowsTheme.detectCurrent(); this.dpiScale = DpiScale.detectSystem(); this.a11y = AccessibilitySettings.load(); } @Override public Button createButton() { // 1. Instantiate family-specific class WindowsButton button = new WindowsButton(); // 2. Apply family-specific configuration button.applyTheme(theme); button.setDpiScale(dpiScale); button.setHighContrastMode(a11y.isHighContrastEnabled()); button.setFlatAppearance(theme.isModernStyle()); // 3. Return as abstract type (Button, not WindowsButton) return button; } @Override public TextField createTextField() { WindowsTextField field = new WindowsTextField(); field.applyTheme(theme); field.setDpiScale(dpiScale); field.setBorderStyle(theme.getTextFieldBorder()); return field; } @Override public Checkbox createCheckbox() { WindowsCheckbox checkbox = new WindowsCheckbox(); checkbox.applyTheme(theme); checkbox.setDpiScale(dpiScale); checkbox.setCheckStyle(theme.getCheckboxStyle()); return checkbox; } @Override public Menu createMenu() { WindowsMenu menu = new WindowsMenu(); menu.applyTheme(theme); menu.setDpiScale(dpiScale); // Windows: menus are attached to windows, not screen-level menu.setMenuBarLocation(MenuBarLocation.IN_WINDOW); return menu; } @Override public Dialog createDialog() { WindowsDialog dialog = new WindowsDialog(); dialog.applyTheme(theme); dialog.setDpiScale(dpiScale); // Windows convention: OK button before Cancel dialog.setButtonOrder(ButtonOrder.OK_CANCEL); return dialog; }} /** * ConcreteFactory: Creates products for the macOS family. * * Different family = different conventions: * - Button order in dialogs is opposite (Cancel, OK) * - Menus appear at screen level, not in windows * - Different animation styles */public class MacOSFactory implements GUIFactory { private final MacOSAppearance appearance; private final RetinaScale retina; public MacOSFactory() { this.appearance = MacOSAppearance.detectCurrent(); this.retina = RetinaScale.detectDisplay(); } @Override public Button createButton() { MacOSButton button = new MacOSButton(); button.setAppearance(appearance); button.setRetinaScale(retina); button.setRoundedStyle(true); // macOS signature button.setHasPulseAnimation(true); // Pulse on focus return button; } @Override public TextField createTextField() { MacOSTextField field = new MacOSTextField(); field.setAppearance(appearance); field.setRetinaScale(retina); field.setFocusRingEnabled(true); // Blue focus ring return field; } @Override public Checkbox createCheckbox() { MacOSCheckbox checkbox = new MacOSCheckbox(); checkbox.setAppearance(appearance); checkbox.setRetinaScale(retina); checkbox.setAnimated(true); // Smooth animation return checkbox; } @Override public Menu createMenu() { MacOSMenu menu = new MacOSMenu(); menu.setAppearance(appearance); menu.setRetinaScale(retina); // macOS: menus are at the top of the screen menu.setMenuBarLocation(MenuBarLocation.SCREEN_TOP); menu.setShowsAppleMenu(true); return menu; } @Override public Dialog createDialog() { MacOSDialog dialog = new MacOSDialog(); dialog.setAppearance(appearance); dialog.setRetinaScale(retina); // macOS convention: Cancel button before OK (opposite of Windows!) dialog.setButtonOrder(ButtonOrder.CANCEL_OK); dialog.setSheetStyle(true); // Slides from title bar return dialog; }}Notice that WindowsFactory has no imports from macOS or Linux packages, and vice versa. Each factory is isolated to its own family. This is not just good practice—it's a design requirement. Cross-family imports would violate the pattern's encapsulation goals.
AbstractProduct participants define interfaces for product types that can be created by factories. They establish what clients can do with products without knowing their concrete implementations.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
/** * AbstractProduct: Button * * DESIGN CONSIDERATIONS: * * 1. Include only cross-family operations: * - render(), onClick(), setText() — all buttons do this * - NOT: setFlatStyle() — Windows-specific * - NOT: setPulseAnimation() — macOS-specific * * 2. Generic enough for all implementations: * - Don't assume specific rendering technology * - Don't assume specific event systems * * 3. Rich enough for client needs: * - If clients need a behavior, it must be in the interface * - Otherwise clients must downcast (defeats the pattern) */public interface Button { /** * Renders the button to the screen. * Implementation will use platform-specific rendering APIs. */ void render(); /** * Registers a click handler. * @param handler The action to execute when clicked */ void onClick(Runnable handler); /** * Sets the button's display text. * @param text The text to display */ void setText(String text); /** * Gets the button's current text. * @return The current display text */ String getText(); /** * Enables or disables the button. * Disabled buttons don't respond to clicks. * @param enabled true to enable, false to disable */ void setEnabled(boolean enabled); /** * Checks if the button is currently enabled. * @return true if enabled */ boolean isEnabled(); /** * Sets keyboard shortcut for this button. * Implementation maps to platform-specific shortcut system. * @param modifiers Modifier keys (Ctrl, Alt, etc.) * @param key The primary key */ void setShortcut(int modifiers, int key); /** * Sets the button's visual style. * Platform implementations may interpret this differently. * @param style PRIMARY, SECONDARY, DANGER, etc. */ void setStyle(ButtonStyle style);} /** * AbstractProduct: Dialog * * Demonstrates more complex product interface with * platform-specific behavioral differences abstracted away. */public interface Dialog { /** * Shows the dialog modally. * On macOS, this may appear as a sheet sliding from the title bar. * On Windows, this appears as a centered modal window. */ void show(); /** * Hides the dialog. */ void hide(); /** * Sets the dialog title. * @param title The title text */ void setTitle(String title); /** * Sets the dialog's main content. * @param content The component to display in the dialog body */ void setContent(Component content); /** * Adds a button to the dialog's button bar. * Button order is determined by platform conventions, * NOT by the order buttons are added. * * @param button The button to add * @param role CONFIRM, CANCEL, or NEUTRAL */ void addButton(Button button, DialogButtonRole role); /** * Gets the result after dialog is closed. * @return The result indicating how the dialog was closed */ DialogResult getResult(); /** * Registers a handler for dialog close events. * @param handler Called when the dialog closes for any reason */ void onClose(Consumer<DialogResult> handler);}There's inherent tension in AbstractProduct design: too narrow and clients must downcast; too wide and some implementations can't fulfill the contract. The sweet spot includes operations that all families can reasonably implement and that clients actually need.
ConcreteProduct participants implement abstract product interfaces for specific families. They contain the platform-specific code that actually makes things work.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
/** * ConcreteProduct: Windows Button Implementation * * This class knows about Windows-specific: * - Win32 API for rendering * - Windows message loop for events * - Windows DPI scaling * - Windows accessibility APIs */public class WindowsButton implements Button { // Windows-specific state private HWND hwnd; // Win32 window handle private WindowsTheme theme; private DpiScale dpiScale; private boolean flatStyle = false; private boolean highContrast = false; // Common state private String text = ""; private boolean enabled = true; private Runnable clickHandler; private ButtonStyle style = ButtonStyle.SECONDARY; @Override public void render() { if (hwnd == null) { // Create the Win32 button control hwnd = User32.CreateWindowEx( 0, "BUTTON", text, flatStyle ? WS_FLAT : WS_RAISED, // ... position and size from layout ); } // Apply current visual state applyThemeColors(); applyDpiScaling(); if (highContrast) { applyHighContrastColors(); } // Trigger Win32 repaint User32.InvalidateRect(hwnd, null, true); } @Override public void onClick(Runnable handler) { this.clickHandler = handler; // Windows: register WM_COMMAND handler WindowsMessageLoop.registerHandler(hwnd, WM_COMMAND, () -> { if (enabled && clickHandler != null) { clickHandler.run(); } }); } @Override public void setText(String text) { this.text = text; if (hwnd != null) { User32.SetWindowText(hwnd, text); } } @Override public String getText() { return text; } @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (hwnd != null) { User32.EnableWindow(hwnd, enabled); } } @Override public boolean isEnabled() { return enabled; } @Override public void setShortcut(int modifiers, int key) { // Register Windows keyboard accelerator WindowsAccelerator accelerator = new WindowsAccelerator(modifiers, key); AcceleratorTable.register(hwnd, accelerator, clickHandler); } @Override public void setStyle(ButtonStyle style) { this.style = style; // Map to Windows button styles switch (style) { case PRIMARY -> setBSStyle(BS_DEFPUSHBUTTON); case DANGER -> setCustomColors(Colors.RED, Colors.WHITE); default -> setBSStyle(BS_PUSHBUTTON); } } // Windows-specific configuration (called by factory) public void applyTheme(WindowsTheme theme) { this.theme = theme; } public void setDpiScale(DpiScale scale) { this.dpiScale = scale; } public void setFlatAppearance(boolean flat) { this.flatStyle = flat; } public void setHighContrastMode(boolean highContrast) { this.highContrast = highContrast; }} /** * ConcreteProduct: macOS Button Implementation * * Uses Cocoa/AppKit APIs for native macOS appearance. */public class MacOSButton implements Button { // macOS-specific state (Cocoa/AppKit) private NSButton nsButton; private MacOSAppearance appearance; private RetinaScale retinaScale; private boolean rounded = true; private boolean pulseAnimation = false; // Common state private String text = ""; private boolean enabled = true; private Runnable clickHandler; @Override public void render() { if (nsButton == null) { // Create Cocoa button nsButton = NSButton.alloc().init(); nsButton.setBezelStyle( rounded ? NSBezelStyleRounded : NSBezelStyleSquare ); } nsButton.setTitle(text); nsButton.setEnabled(enabled); // Apply retina scaling if (retinaScale.isRetina()) { nsButton.layer().setContentsScale(2.0); } // Apply appearance (light/dark mode) nsButton.setAppearance( appearance.isDarkMode() ? NSAppearance.darkAqua() : NSAppearance.aqua() ); } @Override public void onClick(Runnable handler) { this.clickHandler = handler; // macOS: target-action pattern nsButton.setTarget(this); nsButton.setAction("buttonClicked:"); } // Called by Cocoa runtime public void buttonClicked(NSObject sender) { if (enabled && clickHandler != null) { clickHandler.run(); } } @Override public void setShortcut(int modifiers, int key) { // macOS: key equivalents nsButton.setKeyEquivalent(keyCodeToString(key)); nsButton.setKeyEquivalentModifierMask(mapModifiers(modifiers)); } // macOS-specific configuration public void setRoundedStyle(boolean rounded) { this.rounded = rounded; } public void setHasPulseAnimation(boolean pulse) { this.pulseAnimation = pulse; if (pulse && nsButton != null) { // Add Core Animation pulse effect nsButton.layer().addAnimation( createPulseAnimation(), "focusPulse" ); } }}In real systems, concrete products often wrap native platform APIs (Win32, Cocoa, GTK). This is where the Abstract Factory pattern pays off most—clients write platform-agnostic code while concrete products handle the native integration.
The Client participant uses abstract factories and abstract products to perform its work. It's isolated from all concrete classes and family-specific knowledge.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
/** * Client: Uses abstract factories without knowing concrete types. * * CRITICAL RULES FOR CLIENTS: * * 1. No imports of concrete factories: * - ✓ import GUIFactory; * - ✗ import WindowsFactory; * * 2. No imports of concrete products: * - ✓ import Button; * - ✗ import WindowsButton; * * 3. No instanceof checks for families: * - ✗ if (button instanceof WindowsButton) * * 4. No platform-specific code: * - ✗ if (System.getProperty("os.name").contains("Windows")) */public class FormBuilder { private final GUIFactory factory; // Only abstraction! /** * Constructor injection: factory provided from outside. * The client doesn't know or care which factory it receives. */ public FormBuilder(GUIFactory factory) { this.factory = factory; } /** * Builds a contact form. * This method is completely platform-agnostic. */ public Form buildContactForm() { // Create products through factory TextField nameField = factory.createTextField(); TextField emailField = factory.createTextField(); TextField messageField = factory.createTextField(); Button submitButton = factory.createButton(); Button clearButton = factory.createButton(); // Configure using abstract interfaces nameField.setPlaceholder("Your Name"); emailField.setPlaceholder("Email Address"); emailField.setValidation(Validators.email()); messageField.setPlaceholder("Message"); submitButton.setText("Send"); submitButton.setStyle(ButtonStyle.PRIMARY); submitButton.onClick(this::handleSubmit); clearButton.setText("Clear"); clearButton.onClick(this::handleClear); // Build and return form return new Form( nameField, emailField, messageField, submitButton, clearButton ); } /** * Shows a confirmation dialog. * Works identically on Windows, macOS, Linux. */ public boolean showConfirmation(String title, String message) { Dialog dialog = factory.createDialog(); Button confirmButton = factory.createButton(); Button cancelButton = factory.createButton(); dialog.setTitle(title); dialog.setContent(new Label(message)); confirmButton.setText("Confirm"); cancelButton.setText("Cancel"); // Note: button ORDER is platform-specific, // but we use ROLES so the platform can order correctly. dialog.addButton(confirmButton, DialogButtonRole.CONFIRM); dialog.addButton(cancelButton, DialogButtonRole.CANCEL); dialog.show(); // Blocks until closed return dialog.getResult() == DialogResult.CONFIRMED; } /** * Builds a complex settings panel with multiple sections. */ public Panel buildSettingsPanel() { // Create nested structure using factory Panel generalSection = buildGeneralSettings(); Panel appearanceSection = buildAppearanceSettings(); Panel advancedSection = buildAdvancedSettings(); // Create section headers Menu sectionsMenu = factory.createMenu(); sectionsMenu.addItem("General", () -> showSection(generalSection)); sectionsMenu.addItem("Appearance", () -> showSection(appearanceSection)); sectionsMenu.addItem("Advanced", () -> showSection(advancedSection)); return new SettingsPanel(sectionsMenu, generalSection, appearanceSection, advancedSection); } private Panel buildGeneralSettings() { Checkbox autoSave = factory.createCheckbox(); Checkbox showWelcome = factory.createCheckbox(); autoSave.setLabel("Auto-save changes"); showWelcome.setLabel("Show welcome screen at startup"); // All checkboxes are configured through abstract interface. // Whether they animate smoothly (macOS) or respond // instantly (Windows) is encapsulated in the product. return new Panel(autoSave, showWelcome); }}A well-designed client has zero knowledge of families or concrete types. If you find yourself adding platform checks or concrete imports to client code, you're violating the pattern. Push that knowledge into factories or products.
Beyond static structure, understanding how participants collaborate at runtime is essential. The following sequence diagram shows the typical interaction flow when a client creates and uses products:
Key Observations from the Sequence:
Bootstrap creates the concrete factory — Family selection happens here, once.
Client receives factory as abstract type — The client holds GUIFactory, not WindowsFactory.
Polymorphic dispatch routes to concrete factory — factory.createButton() calls WindowsFactory.createButton() at runtime.
Factory configures products — Theme and scaling are applied during creation, invisible to client.
Products returned as abstract types — Client receives Button, not WindowsButton.
Client uses abstract interfaces — All operations go through Button and TextField interfaces.
Products render using platform APIs — The actual Windows rendering code is encapsulated in WindowsButton.render().
We've now examined every participant in the Abstract Factory pattern in detail. Let's consolidate this structural knowledge:
What's Next:
In the final page, we'll compare Factory Method vs Abstract Factory in depth. While both are creational patterns involving factories, they solve different problems and have different structures. Understanding when to use each is critical for correct pattern selection.
You can now identify and name all participants in the Abstract Factory pattern, understand their responsibilities, and trace object interactions. This structural knowledge enables you to recognize the pattern in codebases and implement it correctly in your own systems.