Factory and Abstract Factory Patterns Explained

Introduction
The Singleton pattern ensures you have one instance. But what about creating many instances — and doing it in a flexible, decoupled way?
The Factory family of patterns is about delegating object creation. Instead of calling new SpecificClass() directly, you call a factory that decides which class to instantiate. This keeps your calling code independent of the concrete classes it uses.
There are two distinct patterns in this family:
| Pattern | What it does |
|---|---|
| Factory Method | Defines an interface for creating an object, but lets subclasses decide which class to instantiate |
| Abstract Factory | Provides an interface for creating families of related objects without specifying their concrete classes |
What You'll Learn
✅ Understand the problem both patterns solve
✅ Implement Factory Method in TypeScript, Python, and Java
✅ Implement Abstract Factory in TypeScript, Python, and Java
✅ Know when to use Factory Method vs Abstract Factory
✅ Explore real-world use cases: UI toolkits, notification systems, parsers
✅ Avoid common anti-patterns around factory misuse
Prerequisites
- Completed Singleton Pattern Explained
- Comfortable with interfaces and abstract classes (Polymorphism and Interfaces)
- Familiar with SOLID Principles, especially Open/Closed
The Problem: Why Not Just Use new?
Consider a notification system:
// ❌ Tightly coupled — hard to extend
class NotificationService {
send(type: string, message: string) {
if (type === 'email') {
const email = new EmailNotification();
email.send(message);
} else if (type === 'sms') {
const sms = new SMSNotification();
sms.send(message);
} else if (type === 'push') {
const push = new PushNotification();
push.send(message);
}
// Every new type requires modifying this class ❌
}
}Problems with this approach:
- Open/Closed Principle violated — every new notification type requires modifying
NotificationService - Tight coupling —
NotificationServiceknows about every concrete class - Hard to test — you can't inject a mock without hacking the internals
- Duplicated logic — if other services also create notifications, this
if/elseis repeated everywhere
The Factory patterns solve this by centralizing and abstracting the object creation logic.
Part 1: Factory Method Pattern
Intent
Define an interface for creating an object, but let subclasses (or implementations) decide which class to instantiate. The factory method defers instantiation to subclasses.
TypeScript Implementation
// Product interface
interface Notification {
send(message: string): void;
getChannel(): string;
}
// Concrete products
class EmailNotification implements Notification {
constructor(private readonly recipient: string) {}
send(message: string): void {
console.log(`📧 Email to ${this.recipient}: ${message}`);
}
getChannel(): string {
return 'email';
}
}
class SMSNotification implements Notification {
constructor(private readonly phoneNumber: string) {}
send(message: string): void {
console.log(`📱 SMS to ${this.phoneNumber}: ${message}`);
}
getChannel(): string {
return 'sms';
}
}
class PushNotification implements Notification {
constructor(private readonly deviceToken: string) {}
send(message: string): void {
console.log(`🔔 Push to ${this.deviceToken}: ${message}`);
}
getChannel(): string {
return 'push';
}
}
// Creator (abstract)
abstract class NotificationCreator {
// Factory Method — subclasses decide what to create
abstract createNotification(recipient: string): Notification;
// Template method using the factory method
notify(recipient: string, message: string): void {
const notification = this.createNotification(recipient);
console.log(`Sending via ${notification.getChannel()}...`);
notification.send(message);
}
}
// Concrete creators
class EmailNotificationCreator extends NotificationCreator {
createNotification(recipient: string): Notification {
return new EmailNotification(recipient);
}
}
class SMSNotificationCreator extends NotificationCreator {
createNotification(recipient: string): Notification {
return new SMSNotification(recipient);
}
}
class PushNotificationCreator extends NotificationCreator {
createNotification(recipient: string): Notification {
return new PushNotification(recipient);
}
}
// Usage
const emailCreator = new EmailNotificationCreator();
emailCreator.notify('user@example.com', 'Your order has shipped!');
// Sending via email...
// 📧 Email to user@example.com: Your order has shipped!
const smsCreator = new SMSNotificationCreator();
smsCreator.notify('+1234567890', 'Your order has shipped!');
// Sending via sms...
// 📱 SMS to +1234567890: Your order has shipped!Simple Factory (Not a GoF Pattern, But Common)
Before covering Abstract Factory, note that many codebases use a Simple Factory — a static method or class that centralizes creation without the inheritance hierarchy. It's simpler but less flexible:
// Simple Factory — not a GoF pattern, but pragmatic
class NotificationFactory {
static create(
type: 'email' | 'sms' | 'push',
recipient: string
): Notification {
switch (type) {
case 'email':
return new EmailNotification(recipient);
case 'sms':
return new SMSNotification(recipient);
case 'push':
return new PushNotification(recipient);
default:
throw new Error(`Unknown notification type: ${type}`);
}
}
}
// Usage — clean, centralized, but still tightly coupled to concrete types
const notification = NotificationFactory.create('email', 'user@example.com');
notification.send('Hello!');When to prefer Simple Factory over Factory Method:
- Small, stable set of types (not growing much)
- No need to extend via subclassing
- Prototyping or smaller applications
Python Implementation
from abc import ABC, abstractmethod
from typing import Protocol
class Notification(Protocol):
def send(self, message: str) -> None: ...
def get_channel(self) -> str: ...
class EmailNotification:
def __init__(self, recipient: str) -> None:
self.recipient = recipient
def send(self, message: str) -> None:
print(f"📧 Email to {self.recipient}: {message}")
def get_channel(self) -> str:
return "email"
class SMSNotification:
def __init__(self, phone_number: str) -> None:
self.phone_number = phone_number
def send(self, message: str) -> None:
print(f"📱 SMS to {self.phone_number}: {message}")
def get_channel(self) -> str:
return "sms"
class NotificationCreator(ABC):
@abstractmethod
def create_notification(self, recipient: str) -> Notification:
pass
def notify(self, recipient: str, message: str) -> None:
notification = self.create_notification(recipient)
print(f"Sending via {notification.get_channel()}...")
notification.send(message)
class EmailNotificationCreator(NotificationCreator):
def create_notification(self, recipient: str) -> Notification:
return EmailNotification(recipient)
class SMSNotificationCreator(NotificationCreator):
def create_notification(self, recipient: str) -> Notification:
return SMSNotification(recipient)
# Usage
creator = EmailNotificationCreator()
creator.notify("user@example.com", "Your order has shipped!")
# Sending via email...
# 📧 Email to user@example.com: Your order has shipped!Java Implementation
// Product interface
public interface Notification {
void send(String message);
String getChannel();
}
// Concrete products
public class EmailNotification implements Notification {
private final String recipient;
public EmailNotification(String recipient) {
this.recipient = recipient;
}
@Override
public void send(String message) {
System.out.println("📧 Email to " + recipient + ": " + message);
}
@Override
public String getChannel() { return "email"; }
}
public class SMSNotification implements Notification {
private final String phoneNumber;
public SMSNotification(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
@Override
public void send(String message) {
System.out.println("📱 SMS to " + phoneNumber + ": " + message);
}
@Override
public String getChannel() { return "sms"; }
}
// Creator (abstract class with factory method)
public abstract class NotificationCreator {
// Factory Method — subclasses override this
public abstract Notification createNotification(String recipient);
// Template method
public void notify(String recipient, String message) {
Notification notification = createNotification(recipient);
System.out.println("Sending via " + notification.getChannel() + "...");
notification.send(message);
}
}
// Concrete creators
public class EmailNotificationCreator extends NotificationCreator {
@Override
public Notification createNotification(String recipient) {
return new EmailNotification(recipient);
}
}
public class SMSNotificationCreator extends NotificationCreator {
@Override
public Notification createNotification(String recipient) {
return new SMSNotification(recipient);
}
}
// Usage
NotificationCreator creator = new EmailNotificationCreator();
creator.notify("user@example.com", "Your order has shipped!");Part 2: Abstract Factory Pattern
Intent
Provide an interface for creating families of related objects without specifying their concrete classes. Think of it as a "factory of factories" — each concrete factory produces a complete, consistent set of related products.
When to Use Abstract Factory vs Factory Method
| Factory Method | Abstract Factory | |
|---|---|---|
| Creates | One product type | Multiple related product types |
| Mechanism | Inheritance (subclass overrides) | Composition (inject factory interface) |
| Use when | One variation point | Multiple consistent variation points |
| Example | Different notification channels | Cross-platform UI (buttons + checkboxes + inputs) |
Classic Example: Cross-Platform UI
TypeScript Implementation
// Abstract products
interface Button {
render(): void;
onClick(handler: () => void): void;
}
interface Checkbox {
render(): void;
toggle(): void;
isChecked(): boolean;
}
interface TextInput {
render(): void;
getValue(): string;
setValue(value: string): void;
}
// Concrete products — Windows family
class WindowsButton implements Button {
private handler?: () => void;
render(): void {
console.log('[Windows] Rendering button with Win32 style');
}
onClick(handler: () => void): void {
this.handler = handler;
}
}
class WindowsCheckbox implements Checkbox {
private checked = false;
render(): void {
console.log(`[Windows] Rendering checkbox (${this.checked ? '✓' : '○'})`);
}
toggle(): void {
this.checked = !this.checked;
}
isChecked(): boolean {
return this.checked;
}
}
class WindowsTextInput implements TextInput {
private value = '';
render(): void {
console.log(`[Windows] Rendering text input: "${this.value}"`);
}
getValue(): string {
return this.value;
}
setValue(value: string): void {
this.value = value;
}
}
// Concrete products — Mac family
class MacButton implements Button {
private handler?: () => void;
render(): void {
console.log('[Mac] Rendering button with macOS style');
}
onClick(handler: () => void): void {
this.handler = handler;
}
}
class MacCheckbox implements Checkbox {
private checked = false;
render(): void {
console.log(`[Mac] Rendering checkbox (${this.checked ? '●' : '○'})`);
}
toggle(): void {
this.checked = !this.checked;
}
isChecked(): boolean {
return this.checked;
}
}
class MacTextInput implements TextInput {
private value = '';
render(): void {
console.log(`[Mac] Rendering text input: "${this.value}"`);
}
getValue(): string {
return this.value;
}
setValue(value: string): void {
this.value = value;
}
}
// Abstract Factory interface
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox;
createTextInput(): TextInput;
}
// Concrete factories — each produces a consistent FAMILY
class WindowsUIFactory implements UIFactory {
createButton(): Button {
return new WindowsButton();
}
createCheckbox(): Checkbox {
return new WindowsCheckbox();
}
createTextInput(): TextInput {
return new WindowsTextInput();
}
}
class MacUIFactory implements UIFactory {
createButton(): Button {
return new MacButton();
}
createCheckbox(): Checkbox {
return new MacCheckbox();
}
createTextInput(): TextInput {
return new MacTextInput();
}
}
// Application — depends only on the abstract factory
class LoginForm {
private button: Button;
private usernameInput: TextInput;
private rememberMe: Checkbox;
constructor(factory: UIFactory) {
this.button = factory.createButton();
this.usernameInput = factory.createTextInput();
this.rememberMe = factory.createCheckbox();
}
render(): void {
this.usernameInput.render();
this.rememberMe.render();
this.button.render();
}
}
// Usage — inject the right factory at startup
function createFactory(): UIFactory {
const os = process.platform;
if (os === 'win32') return new WindowsUIFactory();
if (os === 'darwin') return new MacUIFactory();
return new MacUIFactory(); // default
}
const factory = createFactory();
const loginForm = new LoginForm(factory);
loginForm.render();
// [Mac] Rendering text input: ""
// [Mac] Rendering checkbox (○)
// [Mac] Rendering button with macOS stylePython Implementation
from abc import ABC, abstractmethod
import sys
class Button(ABC):
@abstractmethod
def render(self) -> None: ...
@abstractmethod
def on_click(self, handler) -> None: ...
class Checkbox(ABC):
@abstractmethod
def render(self) -> None: ...
@abstractmethod
def toggle(self) -> None: ...
@abstractmethod
def is_checked(self) -> bool: ...
class TextInput(ABC):
@abstractmethod
def render(self) -> None: ...
@abstractmethod
def get_value(self) -> str: ...
@abstractmethod
def set_value(self, value: str) -> None: ...
# Windows family
class WindowsButton(Button):
def render(self) -> None:
print("[Windows] Rendering button with Win32 style")
def on_click(self, handler) -> None:
self._handler = handler
class WindowsCheckbox(Checkbox):
def __init__(self):
self._checked = False
def render(self) -> None:
symbol = "✓" if self._checked else "○"
print(f"[Windows] Rendering checkbox ({symbol})")
def toggle(self) -> None:
self._checked = not self._checked
def is_checked(self) -> bool:
return self._checked
class WindowsTextInput(TextInput):
def __init__(self):
self._value = ""
def render(self) -> None:
print(f'[Windows] Rendering text input: "{self._value}"')
def get_value(self) -> str:
return self._value
def set_value(self, value: str) -> None:
self._value = value
# Mac family
class MacButton(Button):
def render(self) -> None:
print("[Mac] Rendering button with macOS style")
def on_click(self, handler) -> None:
self._handler = handler
class MacCheckbox(Checkbox):
def __init__(self):
self._checked = False
def render(self) -> None:
symbol = "●" if self._checked else "○"
print(f"[Mac] Rendering checkbox ({symbol})")
def toggle(self) -> None:
self._checked = not self._checked
def is_checked(self) -> bool:
return self._checked
class MacTextInput(TextInput):
def __init__(self):
self._value = ""
def render(self) -> None:
print(f'[Mac] Rendering text input: "{self._value}"')
def get_value(self) -> str:
return self._value
def set_value(self, value: str) -> None:
self._value = value
# Abstract Factory
class UIFactory(ABC):
@abstractmethod
def create_button(self) -> Button: ...
@abstractmethod
def create_checkbox(self) -> Checkbox: ...
@abstractmethod
def create_text_input(self) -> TextInput: ...
class WindowsUIFactory(UIFactory):
def create_button(self) -> Button:
return WindowsButton()
def create_checkbox(self) -> Checkbox:
return WindowsCheckbox()
def create_text_input(self) -> TextInput:
return WindowsTextInput()
class MacUIFactory(UIFactory):
def create_button(self) -> Button:
return MacButton()
def create_checkbox(self) -> Checkbox:
return MacCheckbox()
def create_text_input(self) -> TextInput:
return MacTextInput()
class LoginForm:
def __init__(self, factory: UIFactory) -> None:
self.button = factory.create_button()
self.username_input = factory.create_text_input()
self.remember_me = factory.create_checkbox()
def render(self) -> None:
self.username_input.render()
self.remember_me.render()
self.button.render()
# Usage
factory: UIFactory
if sys.platform == "win32":
factory = WindowsUIFactory()
else:
factory = MacUIFactory()
login_form = LoginForm(factory)
login_form.render()Java Implementation
// Abstract products
public interface Button {
void render();
void onClick(Runnable handler);
}
public interface Checkbox {
void render();
void toggle();
boolean isChecked();
}
public interface TextInput {
void render();
String getValue();
void setValue(String value);
}
// Windows family
public class WindowsButton implements Button {
@Override
public void render() {
System.out.println("[Windows] Rendering button with Win32 style");
}
@Override
public void onClick(Runnable handler) { /* register handler */ }
}
public class WindowsCheckbox implements Checkbox {
private boolean checked = false;
@Override
public void render() {
System.out.println("[Windows] Rendering checkbox (" + (checked ? "✓" : "○") + ")");
}
@Override
public void toggle() { checked = !checked; }
@Override
public boolean isChecked() { return checked; }
}
// Abstract Factory interface
public interface UIFactory {
Button createButton();
Checkbox createCheckbox();
TextInput createTextInput();
}
// Concrete factories
public class WindowsUIFactory implements UIFactory {
@Override
public Button createButton() { return new WindowsButton(); }
@Override
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
@Override
public TextInput createTextInput() { return new WindowsTextInput(); }
}
public class MacUIFactory implements UIFactory {
@Override
public Button createButton() { return new MacButton(); }
@Override
public Checkbox createCheckbox() { return new MacCheckbox(); }
@Override
public TextInput createTextInput() { return new MacTextInput(); }
}
// Application — only depends on the interface
public class LoginForm {
private final Button button;
private final TextInput usernameInput;
private final Checkbox rememberMe;
public LoginForm(UIFactory factory) {
this.button = factory.createButton();
this.usernameInput = factory.createTextInput();
this.rememberMe = factory.createCheckbox();
}
public void render() {
usernameInput.render();
rememberMe.render();
button.render();
}
}
// Usage
UIFactory factory = getFactoryForOS(); // returns Windows or Mac factory
LoginForm form = new LoginForm(factory);
form.render();Real-World Use Case: Database Access Layer
Abstract Factory is ideal for abstracting database-specific implementations:
// Abstract products
interface DatabaseConnection {
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
close(): Promise<void>;
}
interface DatabaseTransaction {
begin(): Promise<void>;
commit(): Promise<void>;
rollback(): Promise<void>;
query<T>(sql: string, params?: unknown[]): Promise<T[]>;
}
interface DatabaseConnectionPool {
acquire(): Promise<DatabaseConnection>;
release(conn: DatabaseConnection): void;
drain(): Promise<void>;
}
// Abstract factory
interface DatabaseFactory {
createConnection(config: DatabaseConfig): DatabaseConnection;
createTransaction(conn: DatabaseConnection): DatabaseTransaction;
createPool(config: PoolConfig): DatabaseConnectionPool;
}
// Concrete factories
class PostgreSQLFactory implements DatabaseFactory {
createConnection(config: DatabaseConfig): DatabaseConnection {
return new PostgreSQLConnection(config);
}
createTransaction(conn: DatabaseConnection): DatabaseTransaction {
return new PostgreSQLTransaction(conn as PostgreSQLConnection);
}
createPool(config: PoolConfig): DatabaseConnectionPool {
return new PostgreSQLPool(config);
}
}
class SQLiteFactory implements DatabaseFactory {
createConnection(config: DatabaseConfig): DatabaseConnection {
return new SQLiteConnection(config);
}
createTransaction(conn: DatabaseConnection): DatabaseTransaction {
return new SQLiteTransaction(conn as SQLiteConnection);
}
createPool(config: PoolConfig): DatabaseConnectionPool {
return new SQLitePool(config); // SQLite handles pooling differently
}
}
// Repository only depends on the abstract interfaces
class UserRepository {
constructor(
private readonly factory: DatabaseFactory,
private readonly config: DatabaseConfig
) {}
async findById(id: number): Promise<User | null> {
const conn = this.factory.createConnection(this.config);
try {
const rows = await conn.query<User>(
'SELECT * FROM users WHERE id = $1', [id]
);
return rows[0] ?? null;
} finally {
await conn.close();
}
}
async createWithTransaction(userData: CreateUserInput): Promise<User> {
const conn = this.factory.createConnection(this.config);
const tx = this.factory.createTransaction(conn);
await tx.begin();
try {
const [user] = await tx.query<User>(
'INSERT INTO users (name, email) VALUES ($1, $2) RETURNING *',
[userData.name, userData.email]
);
await tx.commit();
return user;
} catch (error) {
await tx.rollback();
throw error;
} finally {
await conn.close();
}
}
}
// Usage — switch databases by swapping the factory
const factory = process.env.NODE_ENV === 'test'
? new SQLiteFactory() // Fast in-memory for tests
: new PostgreSQLFactory(); // Production database
const userRepo = new UserRepository(factory, dbConfig);Real-World Use Case: Parser System
Factory Method is a natural fit for parser families:
interface Document {
getContent(): string;
getMetadata(): Record<string, string>;
}
interface DocumentParser {
parse(input: string): Document;
}
// Factory Method pattern — each subclass handles a format
abstract class DocumentProcessor {
// Factory method
protected abstract createParser(): DocumentParser;
// Template method using the factory method
process(input: string): ProcessedDocument {
const parser = this.createParser();
const doc = parser.parse(input);
return this.transform(doc);
}
private transform(doc: Document): ProcessedDocument {
return {
content: doc.getContent().trim(),
metadata: doc.getMetadata(),
processedAt: new Date(),
};
}
}
class JSONDocumentProcessor extends DocumentProcessor {
protected createParser(): DocumentParser {
return new JSONParser();
}
}
class XMLDocumentProcessor extends DocumentProcessor {
protected createParser(): DocumentParser {
return new XMLParser();
}
}
class MarkdownDocumentProcessor extends DocumentProcessor {
protected createParser(): DocumentParser {
return new MarkdownParser();
}
}
// Registry pattern — pick the right processor at runtime
const processorRegistry: Record<string, DocumentProcessor> = {
json: new JSONDocumentProcessor(),
xml: new XMLDocumentProcessor(),
md: new MarkdownDocumentProcessor(),
};
function processDocument(format: string, input: string): ProcessedDocument {
const processor = processorRegistry[format];
if (!processor) throw new Error(`Unsupported format: ${format}`);
return processor.process(input);
}Testing with Factories
One major benefit of factories: easy to test with mocks.
// Test double factory — returns predictable objects
class MockUIFactory implements UIFactory {
createButton(): Button {
return {
render: jest.fn(),
onClick: jest.fn(),
};
}
createCheckbox(): Checkbox {
return {
render: jest.fn(),
toggle: jest.fn(),
isChecked: jest.fn().mockReturnValue(false),
};
}
createTextInput(): TextInput {
return {
render: jest.fn(),
getValue: jest.fn().mockReturnValue(''),
setValue: jest.fn(),
};
}
}
// Test
describe('LoginForm', () => {
it('renders all components on initialization', () => {
const mockFactory = new MockUIFactory();
const form = new LoginForm(mockFactory);
form.render();
// Verify each component was asked to render
// (cast to get access to jest mock methods)
const button = mockFactory.createButton();
expect(button.render).toHaveBeenCalled();
});
});
// Similarly for the database example:
class MockDatabaseFactory implements DatabaseFactory {
createConnection(): DatabaseConnection {
return {
query: jest.fn().mockResolvedValue([]),
close: jest.fn().mockResolvedValue(undefined),
};
}
createTransaction(conn: DatabaseConnection): DatabaseTransaction {
return {
begin: jest.fn().mockResolvedValue(undefined),
commit: jest.fn().mockResolvedValue(undefined),
rollback: jest.fn().mockResolvedValue(undefined),
query: jest.fn().mockResolvedValue([]),
};
}
createPool(): DatabaseConnectionPool {
return {
acquire: jest.fn(),
release: jest.fn(),
drain: jest.fn().mockResolvedValue(undefined),
};
}
}
describe('UserRepository', () => {
it('returns null when user not found', async () => {
const factory = new MockDatabaseFactory();
const repo = new UserRepository(factory, testConfig);
const user = await repo.findById(999);
expect(user).toBeNull();
});
});When to Use Each Pattern
Summary
| Scenario | Pattern to Use |
|---|---|
| Create one product, subclasses decide which | Factory Method |
| Create a family of consistent products | Abstract Factory |
| Small, fixed set of types, no subclassing | Simple Factory |
| Need to swap entire product families (OS, DB, theme) | Abstract Factory |
| Framework expects you to override a creation method | Factory Method |
Common Pitfalls
1. Over-engineering with Abstract Factory
// ❌ Overkill — Abstract Factory for a single product type
interface ButtonFactory {
createButton(): Button; // Only one product? Use Factory Method instead
}
// ✅ Abstract Factory is for families of products
interface UIFactory {
createButton(): Button;
createCheckbox(): Checkbox; // Multiple related products
createTextInput(): TextInput; // that need to stay consistent
}2. Factories that know too much
// ❌ Factory with business logic — violates Single Responsibility
class UserFactory {
static create(data: UserInput): User {
const user = new User(data);
user.sendWelcomeEmail(); // ❌ Not the factory's job
database.save(user); // ❌ Not the factory's job
auditLog.record(user); // ❌ Not the factory's job
return user;
}
}
// ✅ Factory only creates
class UserFactory {
static create(data: UserInput): User {
return new User(data);
}
}3. Forgetting to register new types
// ❌ Registry-based factory that doesn't error loudly
class ParserFactory {
private static parsers: Map<string, DocumentParser> = new Map([
['json', new JSONParser()],
['xml', new XMLParser()],
// New developer adds MarkdownParser but forgets to register here
]);
static create(format: string): DocumentParser {
return this.parsers.get(format) ?? new JSONParser(); // ❌ Silent fallback
}
}
// ✅ Fail loudly
static create(format: string): DocumentParser {
const parser = this.parsers.get(format);
if (!parser) {
throw new Error(
`No parser registered for format: "${format}". ` +
`Registered formats: ${[...this.parsers.keys()].join(', ')}`
);
}
return parser;
}Summary and Key Takeaways
Factory Method:
✅ Centralizes creation of one product type
✅ Lets subclasses decide what concrete class to instantiate
✅ Great for frameworks where users override creation hooks
✅ Follows Open/Closed Principle — add new products without modifying existing creators
Abstract Factory:
✅ Creates families of related products that must be consistent
✅ The client depends only on abstract interfaces — never on concrete classes
✅ Perfect for cross-platform, multi-database, or multi-theme systems
✅ Makes it trivial to swap entire families (e.g., Windows → Mac UI)
What's Next
- OOP-10: Builder Pattern — when objects have many optional parts and complex assembly steps
- OOP-12: Adapter and Facade Patterns — structural patterns for working with incompatible interfaces
- OOP-13: Decorator and Proxy Patterns — adding behavior dynamically without subclassing
📬 Subscribe to Newsletter
Get the latest blog posts delivered to your inbox every week. No spam, unsubscribe anytime.
We respect your privacy. Unsubscribe at any time.
💬 Comments
Sign in to leave a comment
We'll never post without your permission.