Factory Pattern - Creating Objects Without Specifying Exact Classes
Learning Objectives: By the end of this lesson, you will be able to:
- Understand the different types of Factory patterns (Simple Factory, Factory Method, Abstract Factory)
- Implement Factory patterns to create objects without tight coupling to concrete classes
- Apply Factory patterns in real-world scenarios like database drivers and UI components
- Recognize when to use each Factory variant based on complexity and requirements
- Refactor existing code to use Factory patterns for better maintainability and extensibility
1. Welcome and Introduction (5 minutes)
Icebreaker Activity
Think about ordering food from different restaurants through a delivery app. You don’t need to know how each restaurant prepares their food - you just specify what you want, and the right kitchen creates it. How is this similar to object creation in programming?
Lesson Overview
- Duration: 120 minutes
- Prerequisites: Object-oriented programming, inheritance, interfaces/abstract classes
- Materials Needed: Development environment with Java, Python, or JavaScript support
2. Learning Objectives and Session Outline (5 minutes)
What We’ll Cover Today:
- Factory Pattern Motivation - Problems with direct object instantiation
- Simple Factory - Centralizing object creation logic
- Factory Method Pattern - Letting subclasses decide which class to instantiate
- Abstract Factory Pattern - Creating families of related objects
- Real-world Applications - Database connections, UI components, plugin systems
- Design Trade-offs - When to use each pattern and complexity considerations
Success Criteria:
By the end of this session, you should be able to choose the appropriate Factory pattern for different scenarios and implement it effectively.
3. Foundation Knowledge (15-20 minutes)
Core Concepts
What is the Factory Pattern?
The Factory pattern is a creational design pattern that provides an interface for creating objects without specifying their exact classes. Instead of calling constructors directly, client code requests objects from a factory, which decides which concrete class to instantiate.
Real-World Analogy: Consider a car manufacturing plant. You order a “sedan” or “SUV” without knowing the specific assembly process. The factory manager (Factory) decides whether to build a Honda Civic, Toyota Camry, or Ford Explorer based on your requirements.
Why This Matters:
- Decoupling: Client code doesn’t depend on concrete classes
- Flexibility: Easy to add new product types without changing existing code
- Consistency: Centralized creation logic ensures proper object initialization
- Polymorphism: Clients work with interfaces, enabling substitutability
Problems Factory Pattern Solves
Problem 1: Tight Coupling
// Bad: Client directly depends on concrete classes
public class BadOrderProcessor {
public void processPayment(String type, double amount) {
if (type.equals("CREDIT_CARD")) {
CreditCardProcessor processor = new CreditCardProcessor();
processor.process(amount);
} else if (type.equals("PAYPAL")) {
PayPalProcessor processor = new PayPalProcessor();
processor.process(amount);
} else if (type.equals("BANK_TRANSFER")) {
BankTransferProcessor processor = new BankTransferProcessor();
processor.process(amount);
}
// Adding new payment method requires modifying this code
}
}
Problem 2: Complex Object Creation
// Bad: Complex initialization scattered throughout codebase
DatabaseConnection connection = new PostgreSQLConnection();
connection.setHost("localhost");
connection.setPort(5432);
connection.setDatabase("myapp");
connection.setUsername("user");
connection.setPassword("password");
connection.setConnectionPool(true);
connection.setMaxConnections(20);
connection.configure();
connection.connect();
Key Terminology
- Product: Abstract interface or base class for objects being created
- Concrete Product: Specific implementations of the product interface
- Creator: Abstract class or interface that declares the factory method
- Concrete Creator: Implements the factory method to create specific products
- Factory Method: Method responsible for creating and returning product instances
4. Detailed Explanation and Examples (25-30 minutes)
Understanding Factory Pattern Variants
1. Simple Factory (Not GoF Pattern, but Commonly Used)
The Simple Factory encapsulates object creation in a single class with a static method.
// Product interface
interface Vehicle {
void start();
void stop();
String getType();
}
// Concrete products
class Car implements Vehicle {
public void start() { System.out.println("Car engine started"); }
public void stop() { System.out.println("Car engine stopped"); }
public String getType() { return "Car"; }
}
class Motorcycle implements Vehicle {
public void start() { System.out.println("Motorcycle engine started"); }
public void stop() { System.out.println("Motorcycle engine stopped"); }
public String getType() { return "Motorcycle"; }
}
class Truck implements Vehicle {
public void start() { System.out.println("Truck engine started"); }
public void stop() { System.out.println("Truck engine stopped"); }
public String getType() { return "Truck"; }
}
// Simple Factory
class VehicleFactory {
public static Vehicle createVehicle(String type) {
switch (type.toUpperCase()) {
case "CAR":
return new Car();
case "MOTORCYCLE":
return new Motorcycle();
case "TRUCK":
return new Truck();
default:
throw new IllegalArgumentException("Unknown vehicle type: " + type);
}
}
}
// Client usage
public class VehicleRental {
public void rentVehicle(String vehicleType) {
Vehicle vehicle = VehicleFactory.createVehicle(vehicleType);
vehicle.start();
System.out.println("Renting " + vehicle.getType());
// Use vehicle...
vehicle.stop();
}
}
2. Factory Method Pattern (GoF Pattern)
The Factory Method pattern defines an interface for creating objects, but lets subclasses decide which class to instantiate.
// Product interface
interface Dialog {
void render();
void onClick();
}
// Concrete products
class WindowsDialog implements Dialog {
public void render() {
System.out.println("Rendering Windows-style dialog");
}
public void onClick() {
System.out.println("Windows dialog click handled");
}
}
class MacDialog implements Dialog {
public void render() {
System.out.println("Rendering Mac-style dialog");
}
public void onClick() {
System.out.println("Mac dialog click handled");
}
}
class LinuxDialog implements Dialog {
public void render() {
System.out.println("Rendering Linux-style dialog");
}
public void onClick() {
System.out.println("Linux dialog click handled");
}
}
// Creator abstract class
abstract class Application {
// Factory method - subclasses will implement this
protected abstract Dialog createDialog();
// Template method that uses the factory method
public void showDialog() {
Dialog dialog = createDialog();
dialog.render();
dialog.onClick();
}
}
// Concrete creators
class WindowsApplication extends Application {
protected Dialog createDialog() {
return new WindowsDialog();
}
}
class MacApplication extends Application {
protected Dialog createDialog() {
return new MacDialog();
}
}
class LinuxApplication extends Application {
protected Dialog createDialog() {
return new LinuxDialog();
}
}
// Client code
public class ApplicationRunner {
private static Application configureApplication() {
String osName = System.getProperty("os.name").toLowerCase();
if (osName.contains("win")) {
return new WindowsApplication();
} else if (osName.contains("mac")) {
return new MacApplication();
} else {
return new LinuxApplication();
}
}
public static void main(String[] args) {
Application app = configureApplication();
app.showDialog();
}
}
3. Abstract Factory Pattern (GoF Pattern)
The Abstract Factory provides an interface for creating families of related objects.
// Abstract products
interface Button {
void click();
void render();
}
interface Checkbox {
void check();
void render();
}
interface TextField {
void type(String text);
void render();
}
// Windows family
class WindowsButton implements Button {
public void click() { System.out.println("Windows button clicked"); }
public void render() { System.out.println("Rendering Windows button"); }
}
class WindowsCheckbox implements Checkbox {
public void check() { System.out.println("Windows checkbox checked"); }
public void render() { System.out.println("Rendering Windows checkbox"); }
}
class WindowsTextField implements TextField {
public void type(String text) { System.out.println("Typing in Windows field: " + text); }
public void render() { System.out.println("Rendering Windows text field"); }
}
// Mac family
class MacButton implements Button {
public void click() { System.out.println("Mac button clicked"); }
public void render() { System.out.println("Rendering Mac button"); }
}
class MacCheckbox implements Checkbox {
public void check() { System.out.println("Mac checkbox checked"); }
public void render() { System.out.println("Rendering Mac checkbox"); }
}
class MacTextField implements TextField {
public void type(String text) { System.out.println("Typing in Mac field: " + text); }
public void render() { System.out.println("Rendering Mac text field"); }
}
// Abstract factory
interface GUIFactory {
Button createButton();
Checkbox createCheckbox();
TextField createTextField();
}
// Concrete factories
class WindowsFactory implements GUIFactory {
public Button createButton() { return new WindowsButton(); }
public Checkbox createCheckbox() { return new WindowsCheckbox(); }
public TextField createTextField() { return new WindowsTextField(); }
}
class MacFactory implements GUIFactory {
public Button createButton() { return new MacButton(); }
public Checkbox createCheckbox() { return new MacCheckbox(); }
public TextField createTextField() { return new MacTextField(); }
}
// Client code
class Application {
private GUIFactory factory;
private Button button;
private Checkbox checkbox;
private TextField textField;
public Application(GUIFactory factory) {
this.factory = factory;
}
public void createUI() {
button = factory.createButton();
checkbox = factory.createCheckbox();
textField = factory.createTextField();
}
public void renderUI() {
button.render();
checkbox.render();
textField.render();
}
}
Practical Applications
Database Connection Factory
from abc import ABC, abstractmethod
import sqlite3
import psycopg2
from typing import Any
# Product interface
class DatabaseConnection(ABC):
@abstractmethod
def connect(self) -> Any:
pass
@abstractmethod
def execute_query(self, query: str) -> Any:
pass
@abstractmethod
def close(self) -> None:
pass
# Concrete products
class SQLiteConnection(DatabaseConnection):
def __init__(self, database_path: str):
self.database_path = database_path
self.connection = None
def connect(self):
self.connection = sqlite3.connect(self.database_path)
print(f"Connected to SQLite database: {self.database_path}")
return self.connection
def execute_query(self, query: str):
if not self.connection:
self.connect()
cursor = self.connection.cursor()
cursor.execute(query)
return cursor.fetchall()
def close(self):
if self.connection:
self.connection.close()
print("SQLite connection closed")
class PostgreSQLConnection(DatabaseConnection):
def __init__(self, host: str, port: int, database: str, user: str, password: str):
self.host = host
self.port = port
self.database = database
self.user = user
self.password = password
self.connection = None
def connect(self):
self.connection = psycopg2.connect(
host=self.host,
port=self.port,
database=self.database,
user=self.user,
password=self.password
)
print(f"Connected to PostgreSQL database: {self.database}")
return self.connection
def execute_query(self, query: str):
if not self.connection:
self.connect()
cursor = self.connection.cursor()
cursor.execute(query)
return cursor.fetchall()
def close(self):
if self.connection:
self.connection.close()
print("PostgreSQL connection closed")
# Factory
class DatabaseConnectionFactory:
@staticmethod
def create_connection(db_type: str, **kwargs) -> DatabaseConnection:
if db_type.lower() == 'sqlite':
return SQLiteConnection(kwargs.get('database_path', 'default.db'))
elif db_type.lower() == 'postgresql':
return PostgreSQLConnection(
host=kwargs.get('host', 'localhost'),
port=kwargs.get('port', 5432),
database=kwargs.get('database', 'postgres'),
user=kwargs.get('user', 'postgres'),
password=kwargs.get('password', '')
)
else:
raise ValueError(f"Unsupported database type: {db_type}")
# Usage
def demonstrate_database_factory():
# Create SQLite connection
sqlite_conn = DatabaseConnectionFactory.create_connection(
'sqlite',
database_path='app.db'
)
sqlite_conn.connect()
sqlite_conn.execute_query("SELECT * FROM users")
sqlite_conn.close()
# Create PostgreSQL connection
postgres_conn = DatabaseConnectionFactory.create_connection(
'postgresql',
host='localhost',
database='myapp',
user='dbuser',
password='secret'
)
postgres_conn.connect()
postgres_conn.execute_query("SELECT * FROM customers")
postgres_conn.close()
Common Misconceptions
-
Misconception: Factory patterns are only useful for complex object creation Reality: They’re valuable any time you want to decouple client code from concrete classes
-
Misconception: Simple Factory is always inferior to Factory Method Reality: Simple Factory is often sufficient for straightforward scenarios and easier to understand
-
Misconception: Abstract Factory is always the most flexible option Reality: Abstract Factory adds complexity and should only be used when you need families of related objects
5. Live Coding Demonstration (20-25 minutes)
Example 1: Payment Processing System
// Payment processor interface
class PaymentProcessor {
process(amount) {
throw new Error("Method must be implemented");
}
validate(paymentData) {
throw new Error("Method must be implemented");
}
}
// Concrete payment processors
class CreditCardProcessor extends PaymentProcessor {
process(amount) {
console.log(`Processing $${amount} via Credit Card`);
// Simulate credit card processing
return {
success: true,
transactionId: `CC_${Date.now()}`,
method: 'Credit Card'
};
}
validate(paymentData) {
return paymentData.cardNumber && paymentData.expiryDate && paymentData.cvv;
}
}
class PayPalProcessor extends PaymentProcessor {
process(amount) {
console.log(`Processing $${amount} via PayPal`);
// Simulate PayPal processing
return {
success: true,
transactionId: `PP_${Date.now()}`,
method: 'PayPal'
};
}
validate(paymentData) {
return paymentData.email && paymentData.password;
}
}
class CryptoProcessor extends PaymentProcessor {
process(amount) {
console.log(`Processing $${amount} via Cryptocurrency`);
// Simulate crypto processing
return {
success: true,
transactionId: `CRYPTO_${Date.now()}`,
method: 'Cryptocurrency'
};
}
validate(paymentData) {
return paymentData.walletAddress && paymentData.privateKey;
}
}
// Factory with enum for better type safety
const PaymentType = {
CREDIT_CARD: 'CREDIT_CARD',
PAYPAL: 'PAYPAL',
CRYPTO: 'CRYPTO'
};
class PaymentProcessorFactory {
static create(type) {
switch (type) {
case PaymentType.CREDIT_CARD:
return new CreditCardProcessor();
case PaymentType.PAYPAL:
return new PayPalProcessor();
case PaymentType.CRYPTO:
return new CryptoProcessor();
default:
throw new Error(`Unsupported payment type: ${type}`);
}
}
static getSupportedTypes() {
return Object.values(PaymentType);
}
}
// Payment service using the factory
class PaymentService {
static processPayment(type, amount, paymentData) {
try {
const processor = PaymentProcessorFactory.create(type);
if (!processor.validate(paymentData)) {
throw new Error('Invalid payment data');
}
const result = processor.process(amount);
console.log('Payment processed successfully:', result);
return result;
} catch (error) {
console.error('Payment processing failed:', error.message);
return { success: false, error: error.message };
}
}
}
// Demonstration
console.log('=== Payment Processing Demo ===');
// Process different payment types
PaymentService.processPayment(PaymentType.CREDIT_CARD, 100.00, {
cardNumber: '1234-5678-9012-3456',
expiryDate: '12/25',
cvv: '123'
});
PaymentService.processPayment(PaymentType.PAYPAL, 250.00, {
email: 'user@example.com',
password: 'secret123'
});
PaymentService.processPayment(PaymentType.CRYPTO, 500.00, {
walletAddress: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
privateKey: 'private_key_here'
});
// Show supported types
console.log('Supported payment types:', PaymentProcessorFactory.getSupportedTypes());
Example 2: Document Export Factory Method
from abc import ABC, abstractmethod
from datetime import datetime
import json
import csv
# Product interface
class DocumentExporter(ABC):
@abstractmethod
def export(self, data: dict) -> str:
pass
@abstractmethod
def get_file_extension(self) -> str:
pass
# Concrete products
class JSONExporter(DocumentExporter):
def export(self, data: dict) -> str:
return json.dumps(data, indent=2, default=str)
def get_file_extension(self) -> str:
return '.json'
class CSVExporter(DocumentExporter):
def export(self, data: dict) -> str:
if not data:
return ""
# Convert dict to CSV format
output = []
if isinstance(list(data.values())[0], list):
# Handle multiple records
keys = data.keys()
output.append(','.join(keys))
max_len = max(len(v) for v in data.values())
for i in range(max_len):
row = []
for key in keys:
value = data[key][i] if i < len(data[key]) else ""
row.append(str(value))
output.append(','.join(row))
else:
# Handle single record
output.append(','.join(data.keys()))
output.append(','.join(str(v) for v in data.values()))
return '\n'.join(output)
def get_file_extension(self) -> str:
return '.csv'
class XMLExporter(DocumentExporter):
def export(self, data: dict) -> str:
def dict_to_xml(d, root_name='root'):
xml = f'<{root_name}>\n'
for key, value in d.items():
if isinstance(value, dict):
xml += f' <{key}>\n'
for k, v in value.items():
xml += f' <{k}>{v}</{k}>\n'
xml += f' </{key}>\n'
elif isinstance(value, list):
xml += f' <{key}>\n'
for item in value:
xml += f' <item>{item}</item>\n'
xml += f' </{key}>\n'
else:
xml += f' <{key}>{value}</{key}>\n'
xml += f'</{root_name}>'
return xml
return dict_to_xml(data)
def get_file_extension(self) -> str:
return '.xml'
# Creator abstract class (Factory Method pattern)
class DocumentProcessor(ABC):
@abstractmethod
def create_exporter(self) -> DocumentExporter:
pass
def process_and_export(self, data: dict, filename: str) -> str:
exporter = self.create_exporter()
exported_content = exporter.export(data)
full_filename = filename + exporter.get_file_extension()
# Simulate file writing
print(f"Exporting to {full_filename}")
print("Content preview:")
print(exported_content[:200] + "..." if len(exported_content) > 200 else exported_content)
print("-" * 50)
return exported_content
# Concrete creators
class JSONProcessor(DocumentProcessor):
def create_exporter(self) -> DocumentExporter:
return JSONExporter()
class CSVProcessor(DocumentProcessor):
def create_exporter(self) -> DocumentExporter:
return CSVExporter()
class XMLProcessor(DocumentProcessor):
def create_exporter(self) -> DocumentExporter:
return XMLExporter()
# Factory for processors
class ProcessorFactory:
@staticmethod
def create_processor(format_type: str) -> DocumentProcessor:
processors = {
'json': JSONProcessor,
'csv': CSVProcessor,
'xml': XMLProcessor
}
processor_class = processors.get(format_type.lower())
if not processor_class:
raise ValueError(f"Unsupported format: {format_type}")
return processor_class()
# Demonstration
def demonstrate_document_export():
# Sample data
sample_data = {
'employees': [
{'name': 'John Doe', 'dept': 'Engineering', 'salary': 85000},
{'name': 'Jane Smith', 'dept': 'Marketing', 'salary': 75000},
{'name': 'Bob Johnson', 'dept': 'Sales', 'salary': 65000}
],
'company': 'TechCorp Inc.',
'export_date': datetime.now()
}
formats = ['json', 'csv', 'xml']
for format_type in formats:
try:
processor = ProcessorFactory.create_processor(format_type)
processor.process_and_export(sample_data, f'employee_report')
except ValueError as e:
print(f"Error: {e}")
# Run demonstration
demonstrate_document_export()
Debugging Demonstration
Show common errors and how to fix them:
// Common mistake: Forgetting to handle new types
public class IncompleteFactory {
public static PaymentProcessor create(String type) {
if (type.equals("CREDIT_CARD")) {
return new CreditCardProcessor();
} else if (type.equals("PAYPAL")) {
return new PayPalProcessor();
}
// Bug: No handling for new types like "CRYPTO"
return null; // This will cause NullPointerException!
}
}
// Better approach: Throw exception for unknown types
public class BetterFactory {
public static PaymentProcessor create(String type) {
switch (type) {
case "CREDIT_CARD":
return new CreditCardProcessor();
case "PAYPAL":
return new PayPalProcessor();
case "CRYPTO":
return new CryptoProcessor();
default:
throw new IllegalArgumentException("Unknown payment type: " + type);
}
}
}
6. Hands-On Practice (30-40 minutes)
Exercise 1: Basic Practice (10 minutes)
Objective: Create a Simple Factory for different types of notifications
Instructions:
- Create a
Notificationinterface withsend(String message)method - Implement concrete classes:
EmailNotification,SMSNotification,PushNotification - Create a
NotificationFactorywith a static method to create notifications - Test the factory with different notification types
Starter Code:
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
// TODO: Implement send method
}
class SMSNotification implements Notification {
// TODO: Implement send method
}
class PushNotification implements Notification {
// TODO: Implement send method
}
class NotificationFactory {
// TODO: Implement factory method
public static Notification createNotification(String type) {
// TODO: Return appropriate notification type
return null;
}
}
Solution:
interface Notification {
void send(String message);
}
class EmailNotification implements Notification {
public void send(String message) {
System.out.println("Email sent: " + message);
}
}
class SMSNotification implements Notification {
public void send(String message) {
System.out.println("SMS sent: " + message);
}
}
class PushNotification implements Notification {
public void send(String message) {
System.out.println("Push notification sent: " + message);
}
}
class NotificationFactory {
public enum NotificationType {
EMAIL, SMS, PUSH
}
public static Notification createNotification(NotificationType type) {
switch (type) {
case EMAIL:
return new EmailNotification();
case SMS:
return new SMSNotification();
case PUSH:
return new PushNotification();
default:
throw new IllegalArgumentException("Unknown notification type: " + type);
}
}
}
Exercise 2: Intermediate Challenge (15 minutes)
Objective: Implement Factory Method pattern for different logging systems
Requirements:
- Create abstract
Loggerclass with abstractcreateLogHandler()method - Implement concrete loggers:
FileLogger,DatabaseLogger,ConsoleLogger - Each logger should create appropriate handler (FileHandler, DatabaseHandler, ConsoleHandler)
- Add log levels (INFO, WARNING, ERROR) and formatting
Challenge Features:
- Add timestamp formatting
- Implement log filtering by level
- Create configuration for each logger type
Exercise 3: Creative Application (15 minutes)
Objective: Design an Abstract Factory for different game environments
Challenge: Create an Abstract Factory that produces:
- Different enemy types (Zombie, Alien, Robot)
- Different weapons (Pistol, Rifle, Laser)
- Different power-ups (Health, Shield, Speed)
Game Environments:
- Medieval (produces medieval-themed variants)
- SciFi (produces futuristic variants)
- Modern (produces contemporary variants)
Extension Ideas:
- Add difficulty levels that affect enemy strength
- Implement weapon upgrade systems
- Create environment-specific sound effects and graphics
7. Common Pitfalls and Troubleshooting (10 minutes)
Frequent Mistakes
-
Problem: Not handling unknown types in factory methods Cause: Forgetting to add proper error handling for invalid input Solution: Always throw meaningful exceptions for unsupported types
-
Problem: Violating Open/Closed Principle with Simple Factory Cause: Adding new types requires modifying the factory class Solution: Use Factory Method or Abstract Factory for better extensibility
-
Problem: Creating too many factory classes Cause: Overusing patterns when simple constructors would suffice Solution: Only use factories when you need decoupling or complex creation logic
-
Problem: Not making factory methods thread-safe Cause: Shared state in factory without proper synchronization Solution: Use stateless factories or proper synchronization mechanisms
Debugging Tips
- Log factory method calls to trace object creation patterns
- Use unit tests to verify correct object types are created
- Test edge cases like null inputs and invalid type parameters
- Monitor memory usage if factories cache created objects
When to Use Each Factory Type
- Simple Factory: When you have a few concrete classes and simple creation logic
- Factory Method: When you want subclasses to control object creation
- Abstract Factory: When you need to create families of related objects
8. Best Practices and Professional Tips (5 minutes)
Industry Standards
- Use enums instead of strings for type parameters to avoid typos and enable compile-time checking
- Return interfaces, not concrete classes from factory methods for better abstraction
- Consider using dependency injection frameworks for more sophisticated object creation
- Document the lifecycle of objects created by factories
Next Steps in Your Learning Journey
- Study Builder pattern for complex object construction with many optional parameters
- Learn Prototype pattern for creating objects by cloning existing instances
- Explore Dependency Injection containers as modern alternatives to factories
- Investigate Service Locator pattern for service discovery and creation
Modern Alternatives and Considerations
- Dependency Injection: Spring, Guice, Dagger provide more sophisticated object creation
- Functional Approaches: Higher-order functions can sometimes replace factory patterns
- Configuration-driven Factories: Use external configuration to determine object types
- Plugin Architectures: Dynamic loading of implementations at runtime
9. Summary and Review (5 minutes)
Key Takeaways
- Factory patterns decouple object creation from client code, improving flexibility
- Choose the right factory type based on complexity: Simple Factory → Factory Method → Abstract Factory
- Always handle invalid inputs with meaningful error messages
- Use factories when you need polymorphism, decoupling, or complex object creation logic
Quick Review Questions
- What’s the main difference between Factory Method and Abstract Factory patterns?
- When would you choose Simple Factory over Factory Method pattern?
- How do Factory patterns support the Open/Closed Principle?
For Instructors
Teaching Tips
- Start with Simple Factory as it’s easiest to understand, then progress to more complex variants
- Use real-world analogies (restaurants, manufacturing) to explain the concept
- Show refactoring examples where factory patterns improve existing code
- Emphasize when NOT to use factories to avoid over-engineering
Assessment Ideas
- Code refactoring exercise: Transform tightly-coupled code to use factory patterns
- Design challenge: Choose appropriate factory type for given requirements
- Implementation review: Identify and fix problems in factory implementations
Adaptation Notes
- Beginners: Focus on Simple Factory and basic Factory Method usage
- Advanced: Discuss performance implications, memory management, and integration with DI frameworks
- Language-specific: Show language-specific features like Python’s
__new__or JavaScript’s prototype system