Overview
The Factory Method Pattern addresses the problem of creating objects without specifying their exact classes. Instead of calling a constructor directly, the pattern delegates object creation to a method that subclasses can override to change the type of objects created. This separates the construction logic from the code that uses the objects.
The pattern emerged from the Gang of Four design patterns catalog as a solution to the rigid coupling between client code and concrete classes. When a system must work with different object types but the specific type depends on runtime conditions or configuration, direct instantiation creates inflexible dependencies. The Factory Method Pattern breaks this coupling by introducing an abstraction layer for object creation.
Consider a document editor that handles multiple file formats. Direct instantiation would require code like this:
class DocumentEditor
def open_document(file_path)
case File.extname(file_path)
when '.pdf'
PDFDocument.new(file_path)
when '.docx'
WordDocument.new(file_path)
when '.txt'
TextDocument.new(file_path)
end
end
end
This approach violates the Open/Closed Principle because adding new document types requires modifying the editor class. The Factory Method Pattern solves this by delegating creation to subclasses:
class DocumentEditor
def open_document(file_path)
document = create_document(file_path)
document.load
document
end
def create_document(file_path)
raise NotImplementedError, 'Subclasses must implement create_document'
end
end
class PDFEditor < DocumentEditor
def create_document(file_path)
PDFDocument.new(file_path)
end
end
The pattern consists of four main participants: the Product interface defines what the factory creates, Concrete Products implement this interface, the Creator declares the factory method, and Concrete Creators override the factory method to instantiate specific products. This structure enables extending the system with new product types without modifying existing code.
Key Principles
The Factory Method Pattern operates on the principle of dependency inversion. High-level modules depend on abstractions rather than concrete implementations. The creator class depends on a product interface, not specific product classes. Subclasses inject their dependencies by overriding the factory method to return different concrete products.
The pattern introduces polymorphism at the creation level. Where traditional object-oriented programming applies polymorphism to object behavior, the Factory Method Pattern applies it to object construction. The same factory method call produces different object types depending on which subclass handles the call. This creates a parallel class hierarchy: one hierarchy for creators and another for products.
Three core mechanisms enable the pattern. First, the factory method provides a hook that subclasses override. The base class calls this method without knowing which concrete class it instantiates. Second, the base class contains template methods that use the product objects. These template methods define the algorithm structure while delegating specific steps to the factory method. Third, subclasses control product creation by implementing the factory method to return their specific product type.
The pattern embodies the Template Method Pattern for object creation. The creator class defines the skeleton of an operation that includes object creation, while subclasses fill in the details by providing concrete products. Consider a game engine that manages different enemy types:
class GameLevel
def spawn_enemy(position)
enemy = create_enemy
enemy.position = position
enemy.activate
@enemies << enemy
enemy
end
def create_enemy
raise NotImplementedError
end
end
class BeginnerLevel < GameLevel
def create_enemy
SlowEnemy.new
end
end
class ExpertLevel < GameLevel
def create_enemy
FastEnemy.new
end
end
The factory method must return objects that conform to a common interface. This interface defines the contract that all products must satisfy. The creator class interacts with products only through this interface, maintaining loose coupling. The interface may be a formal class, a module, or an implicit duck type in dynamic languages.
Subclass responsibility constitutes a key principle. The pattern shifts instantiation decisions from the creator to its subclasses. Each subclass encapsulates knowledge about which concrete product to create. This localization of creation logic makes the system more maintainable because each subclass controls only its own product type.
The pattern supports the Single Responsibility Principle by separating creation logic from business logic. The creator class focuses on using products to perform operations, while subclasses focus on constructing appropriate products. This separation clarifies responsibilities and improves code organization.
Parameterized factory methods extend the basic pattern by accepting arguments that guide product selection. The factory method uses these parameters to determine which concrete product to instantiate:
class DocumentProcessor
def process(file_path, options = {})
document = create_document(file_path, options)
document.process
end
def create_document(file_path, options)
raise NotImplementedError
end
end
class SmartProcessor < DocumentProcessor
def create_document(file_path, options)
case options[:format]
when :optimized
OptimizedDocument.new(file_path)
when :compressed
CompressedDocument.new(file_path)
else
StandardDocument.new(file_path)
end
end
end
Ruby Implementation
Ruby's dynamic nature and module system provide multiple approaches to implementing the Factory Method Pattern. The classical implementation uses inheritance with abstract methods, but Ruby also supports module-based mixins and block-based factories that offer more flexibility.
The inheritance-based approach mirrors the traditional pattern structure. The base class defines an abstract factory method that raises an error if not overridden:
class DataExporter
def export(data, output_path)
formatter = create_formatter
formatted_data = formatter.format(data)
File.write(output_path, formatted_data)
end
def create_formatter
raise NotImplementedError, "#{self.class} must implement create_formatter"
end
end
class JSONExporter < DataExporter
def create_formatter
JSONFormatter.new
end
end
class XMLExporter < DataExporter
def create_formatter
XMLFormatter.new
end
end
class CSVExporter < DataExporter
def create_formatter
CSVFormatter.new
end
end
Ruby modules enable a mixin-based approach that composes behavior rather than inheriting it. This approach defines factory methods in modules that classes include:
module FormatterFactory
def create_formatter(type)
case type
when :json
JSONFormatter.new
when :xml
XMLFormatter.new
when :csv
CSVFormatter.new
else
raise ArgumentError, "Unknown formatter type: #{type}"
end
end
end
class DataExporter
include FormatterFactory
def initialize(formatter_type)
@formatter_type = formatter_type
end
def export(data, output_path)
formatter = create_formatter(@formatter_type)
formatted_data = formatter.format(data)
File.write(output_path, formatted_data)
end
end
Block-based factories provide a functional approach where factories are first-class objects. This pattern stores creation logic in procs or lambdas:
class ConfigurableProcessor
def initialize(&factory)
@factory = factory || -> { DefaultProcessor.new }
end
def process(data)
processor = @factory.call
processor.process(data)
end
end
# Usage
basic_processor = ConfigurableProcessor.new { BasicProcessor.new }
advanced_processor = ConfigurableProcessor.new { AdvancedProcessor.new }
Registry-based factories map identifiers to product classes, eliminating the need for subclasses. The factory maintains a registry of available product types:
class DocumentFactory
@registry = {}
class << self
def register(format, klass)
@registry[format] = klass
end
def create(format, *args)
klass = @registry[format]
raise ArgumentError, "Unknown format: #{format}" unless klass
klass.new(*args)
end
end
end
DocumentFactory.register(:pdf, PDFDocument)
DocumentFactory.register(:word, WordDocument)
DocumentFactory.register(:text, TextDocument)
document = DocumentFactory.create(:pdf, 'report.pdf')
Ruby's class methods provide another implementation approach. The factory method becomes a class method rather than an instance method:
class ReportGenerator
def self.create(report_type)
case report_type
when :sales
SalesReport.new
when :inventory
InventoryReport.new
when :financial
FinancialReport.new
else
raise ArgumentError, "Unknown report type: #{report_type}"
end
end
end
report = ReportGenerator.create(:sales)
Method objects encapsulate creation logic in dedicated objects. This approach treats factory methods as objects that can be passed around and composed:
class DocumentCreator
def initialize(document_class)
@document_class = document_class
end
def call(file_path)
@document_class.new(file_path)
end
end
class Application
def initialize(document_creator)
@document_creator = document_creator
end
def open_document(file_path)
document = @document_creator.call(file_path)
document.load
document
end
end
# Usage
pdf_creator = DocumentCreator.new(PDFDocument)
app = Application.new(pdf_creator)
Duck typing simplifies factory implementations by eliminating formal interface declarations. Products need only implement the expected methods:
class NotificationSender
def send_notification(message)
notifier = create_notifier
notifier.send(message)
end
def create_notifier
raise NotImplementedError
end
end
class EmailSender < NotificationSender
def create_notifier
# Returns object that responds to send
EmailNotifier.new
end
end
class SMSSender < NotificationSender
def create_notifier
# Returns object that responds to send
SMSNotifier.new
end
end
Design Considerations
The Factory Method Pattern suits scenarios where a class cannot anticipate the types of objects it must create. When subclass-specific instantiation logic exists, the pattern provides a clean way to delegate creation decisions. Systems that frequently add new product types benefit from the pattern's extensibility.
The pattern adds complexity through additional classes and indirection. Simple object creation may not warrant the overhead of factory methods and subclasses. When a system has few product types that rarely change, direct instantiation may suffice. The pattern makes sense when the benefits of flexibility outweigh the cost of additional abstraction.
Abstract Factory Pattern differs from Factory Method Pattern in scope and implementation. Factory Method uses inheritance to vary products, creating one product at a time. Abstract Factory uses composition to create families of related products. Choose Factory Method when varying a single product type across subclasses. Choose Abstract Factory when creating groups of related objects that must be used together.
Parameterized factory methods offer an alternative to multiple subclasses. Instead of creating a subclass for each product type, a single factory method accepts parameters that determine which product to create. This approach reduces class proliferation but concentrates creation logic in one place, potentially violating the Open/Closed Principle:
class FlexibleExporter
def initialize(format)
@format = format
end
def export(data, output_path)
formatter = create_formatter(@format)
formatted_data = formatter.format(data)
File.write(output_path, formatted_data)
end
def create_formatter(format)
case format
when :json then JSONFormatter.new
when :xml then XMLFormatter.new
when :csv then CSVFormatter.new
else raise ArgumentError, "Unsupported format: #{format}"
end
end
end
Simple Factory Pattern provides a simplified alternative that encapsulates object creation in a single class without subclassing. Use this pattern when creation logic does not vary across contexts and the Open/Closed Principle is less critical than simplicity:
class NotificationFactory
def self.create(type)
case type
when :email then EmailNotification.new
when :sms then SMSNotification.new
when :push then PushNotification.new
else raise ArgumentError, "Unknown notification type: #{type}"
end
end
end
Dependency Injection eliminates the need for factory methods by injecting dependencies through constructors or setters. This approach provides more testability and flexibility than factory methods:
class OrderProcessor
def initialize(payment_gateway)
@payment_gateway = payment_gateway
end
def process(order)
@payment_gateway.charge(order.amount)
end
end
# Usage with different gateways
processor = OrderProcessor.new(StripeGateway.new)
# or
processor = OrderProcessor.new(PayPalGateway.new)
Testing considerations influence pattern choice. Factory methods complicate testing because subclasses hard-code product types. Tests must either create subclasses or use dependency injection to substitute test doubles. Parameterized factories and dependency injection offer better testability.
Performance overhead exists but rarely matters. The pattern introduces method calls and potential object allocations. These costs are negligible compared to typical business logic. Only optimize if profiling identifies factory methods as bottlenecks.
Memory considerations apply when products are expensive to create. Factory methods may create new instances on every call, potentially wasting resources. Consider implementing caching or pooling within factory methods when appropriate:
class CachingResourceFactory
def initialize
@cache = {}
end
def create_resource(type)
@cache[type] ||= case type
when :database then DatabaseResource.new
when :api then APIResource.new
when :file then FileResource.new
end
end
end
Practical Examples
An image processing library demonstrates the Factory Method Pattern in action. Different image processors handle various file formats, each requiring format-specific loading and processing logic:
class ImageProcessor
def process(input_path, output_path, options = {})
loader = create_loader
image_data = loader.load(input_path)
processed_data = apply_effects(image_data, options)
loader.save(processed_data, output_path)
end
def create_loader
raise NotImplementedError, "Subclasses must implement create_loader"
end
def apply_effects(image_data, options)
# Common processing logic
image_data.resize(options[:width], options[:height]) if options[:resize]
image_data.adjust_brightness(options[:brightness]) if options[:brightness]
image_data
end
end
class JPEGProcessor < ImageProcessor
def create_loader
JPEGLoader.new(quality: 85)
end
end
class PNGProcessor < ImageProcessor
def create_loader
PNGLoader.new(compression: 9)
end
end
class WebPProcessor < ImageProcessor
def create_loader
WebPLoader.new(lossless: true)
end
end
# Usage
processor = JPEGProcessor.new
processor.process('input.jpg', 'output.jpg', resize: true, width: 800, height: 600)
A database connection library illustrates parameterized factory methods. The factory creates different connection types based on configuration while maintaining a consistent interface:
class DatabaseConnection
def initialize(config)
@config = config
@connection = nil
end
def connect
@connection = create_connection(@config)
@connection.establish
end
def execute(query)
raise 'Not connected' unless @connection
@connection.execute(query)
end
def create_connection(config)
raise NotImplementedError
end
end
class PostgreSQLConnection < DatabaseConnection
def create_connection(config)
PostgreSQLAdapter.new(
host: config[:host],
port: config[:port] || 5432,
database: config[:database],
ssl: config[:ssl] || true
)
end
end
class MySQLConnection < DatabaseConnection
def create_connection(config)
MySQLAdapter.new(
host: config[:host],
port: config[:port] || 3306,
database: config[:database],
charset: config[:charset] || 'utf8mb4'
)
end
end
class SQLiteConnection < DatabaseConnection
def create_connection(config)
SQLiteAdapter.new(
path: config[:path],
timeout: config[:timeout] || 5
)
end
end
# Usage
config = { host: 'localhost', database: 'myapp_production' }
connection = PostgreSQLConnection.new(config)
connection.connect
results = connection.execute('SELECT * FROM users')
A logging system shows how factory methods enable runtime configuration. Different log handlers direct output to various destinations with appropriate formatting:
class Logger
attr_reader :level
def initialize(level = :info)
@level = level
@handler = create_handler
end
def info(message)
log(:info, message) if level_enabled?(:info)
end
def error(message)
log(:error, message) if level_enabled?(:error)
end
def debug(message)
log(:debug, message) if level_enabled?(:debug)
end
private
def log(level, message)
@handler.write(level, message, Time.now)
end
def level_enabled?(message_level)
LEVELS[message_level] >= LEVELS[@level]
end
def create_handler
raise NotImplementedError
end
LEVELS = { debug: 0, info: 1, warn: 2, error: 3 }.freeze
end
class FileLogger < Logger
def initialize(file_path, level = :info)
@file_path = file_path
super(level)
end
private
def create_handler
FileHandler.new(@file_path)
end
end
class ConsoleLogger < Logger
private
def create_handler
ConsoleHandler.new(colorized: true)
end
end
class RemoteLogger < Logger
def initialize(endpoint, api_key, level = :info)
@endpoint = endpoint
@api_key = api_key
super(level)
end
private
def create_handler
RemoteHandler.new(@endpoint, @api_key)
end
end
# Usage
logger = FileLogger.new('application.log', :debug)
logger.info('Application started')
logger.error('Database connection failed')
A payment processing system demonstrates multiple factory methods working together. The system creates appropriate payment gateways and transaction validators based on payment method:
class PaymentProcessor
def process_payment(order)
gateway = create_gateway
validator = create_validator
validation_result = validator.validate(order)
return validation_result unless validation_result.success?
transaction = gateway.charge(
amount: order.total,
currency: order.currency,
description: order.description
)
handle_result(transaction)
end
def create_gateway
raise NotImplementedError
end
def create_validator
raise NotImplementedError
end
private
def handle_result(transaction)
if transaction.successful?
{ success: true, transaction_id: transaction.id }
else
{ success: false, error: transaction.error_message }
end
end
end
class CreditCardProcessor < PaymentProcessor
private
def create_gateway
CreditCardGateway.new(
merchant_id: ENV['MERCHANT_ID'],
api_key: ENV['CC_API_KEY']
)
end
def create_validator
CreditCardValidator.new(
require_cvv: true,
check_fraud: true
)
end
end
class PayPalProcessor < PaymentProcessor
private
def create_gateway
PayPalGateway.new(
client_id: ENV['PAYPAL_CLIENT_ID'],
secret: ENV['PAYPAL_SECRET']
)
end
def create_validator
PayPalValidator.new(
verify_account: true
)
end
end
class CryptocurrencyProcessor < PaymentProcessor
private
def create_gateway
CryptoGateway.new(
network: 'ethereum',
wallet_address: ENV['WALLET_ADDRESS']
)
end
def create_validator
CryptoValidator.new(
min_confirmations: 3,
check_wallet_balance: true
)
end
end
Common Patterns
The Lazy Initialization pattern combines with Factory Method to defer object creation until needed. This pattern improves performance when products are expensive to create or may not be used:
class LazyDataSource
def fetch_data(query)
connection = create_connection
connection.execute(query)
end
private
def create_connection
@connection ||= establish_connection
end
def establish_connection
raise NotImplementedError
end
end
class LazyDatabaseSource < LazyDataSource
private
def establish_connection
DatabaseConnection.new(
host: ENV['DB_HOST'],
database: ENV['DB_NAME']
).tap(&:connect)
end
end
The Registry pattern maintains a mapping of identifiers to factory methods or product classes. This pattern eliminates conditional logic and enables runtime registration:
class PluginManager
@registry = {}
class << self
def register(plugin_type, factory_method)
@registry[plugin_type] = factory_method
end
def create_plugin(plugin_type, *args)
factory = @registry[plugin_type]
raise ArgumentError, "Unknown plugin: #{plugin_type}" unless factory
factory.call(*args)
end
end
end
# Register plugins
PluginManager.register(:auth, ->(opts) { AuthPlugin.new(opts) })
PluginManager.register(:cache, ->(opts) { CachePlugin.new(opts) })
PluginManager.register(:logging, ->(opts) { LoggingPlugin.new(opts) })
# Create plugins
auth_plugin = PluginManager.create_plugin(:auth, strict: true)
The Prototype pattern works with Factory Method when products are expensive to create from scratch. The factory method clones prototype objects instead of constructing new ones:
class DocumentFactory
def initialize
@prototypes = {}
end
def register_prototype(type, prototype)
@prototypes[type] = prototype
end
def create_document(type)
prototype = @prototypes[type]
raise ArgumentError, "No prototype for #{type}" unless prototype
prototype.clone
end
end
class Document
attr_accessor :template, :styles
def initialize(template, styles)
@template = template
@styles = styles
end
def clone
# Deep clone of complex object
Marshal.load(Marshal.dump(self))
end
end
factory = DocumentFactory.new
factory.register_prototype(:invoice, Document.new('invoice.html', { font: 'Arial' }))
factory.register_prototype(:receipt, Document.new('receipt.html', { font: 'Helvetica' }))
document = factory.create_document(:invoice)
The Chain of Responsibility pattern combines with Factory Method when multiple factories can potentially create a product. Each factory attempts creation and passes to the next if unable:
class ParserChain
def initialize
@parsers = []
end
def add_parser(parser)
@parsers << parser
end
def parse(data)
@parsers.each do |parser|
result = parser.try_parse(data)
return result if result
end
raise "No parser could handle the data"
end
end
class BaseParser
def try_parse(data)
return nil unless can_parse?(data)
parse(data)
end
def can_parse?(data)
raise NotImplementedError
end
def parse(data)
raise NotImplementedError
end
end
class JSONParser < BaseParser
def can_parse?(data)
data.strip.start_with?('{', '[')
end
def parse(data)
JSON.parse(data)
end
end
class XMLParser < BaseParser
def can_parse?(data)
data.strip.start_with?('<')
end
def parse(data)
Nokogiri::XML(data)
end
end
The Strategy pattern integrates with Factory Method when different algorithms require different supporting objects. The factory method creates strategy-specific dependencies:
class DataCompressor
def compress(data)
algorithm = create_algorithm
metadata = create_metadata
compressed = algorithm.compress(data)
metadata.record(original_size: data.size, compressed_size: compressed.size)
{ data: compressed, metadata: metadata }
end
def create_algorithm
raise NotImplementedError
end
def create_metadata
raise NotImplementedError
end
end
class GZIPCompressor < DataCompressor
private
def create_algorithm
GZIPAlgorithm.new(level: 6)
end
def create_metadata
GZIPMetadata.new
end
end
class BrotliCompressor < DataCompressor
private
def create_algorithm
BrotliAlgorithm.new(quality: 11)
end
def create_metadata
BrotliMetadata.new
end
end
Common Pitfalls
Creating factory methods that return nil or different types violates the Liskov Substitution Principle. Every factory method must return an object that conforms to the expected interface. Returning nil forces clients to add null checks:
# Problematic implementation
class ReportGenerator
def generate_report(type)
report = create_report(type)
return unless report # Null check required
report.generate
end
def create_report(type)
case type
when :sales then SalesReport.new
when :unknown then nil # Breaks contract
end
end
end
# Correct implementation
class ReportGenerator
def generate_report(type)
report = create_report(type)
report.generate # No null check needed
end
def create_report(type)
case type
when :sales then SalesReport.new
else raise ArgumentError, "Unknown report type: #{type}"
end
end
end
Overusing factory methods for simple object creation adds unnecessary complexity. Direct instantiation suffices when no variation exists across contexts:
# Unnecessary factory method
class UserService
def create_user(name, email)
user = create_user_instance(name, email)
user.save
end
private
def create_user_instance(name, email)
User.new(name: name, email: email)
end
end
# Simpler direct approach
class UserService
def create_user(name, email)
user = User.new(name: name, email: email)
user.save
end
end
Placing business logic inside factory methods mixes concerns and makes testing difficult. Factory methods should focus solely on construction:
# Problematic - business logic in factory
class OrderProcessor
private
def create_shipping_service
service = ShippingService.new
service.calculate_rates # Business logic
service.apply_discounts # Business logic
service
end
end
# Correct - factory creates, caller uses
class OrderProcessor
def process_shipping(order)
service = create_shipping_service
service.calculate_rates(order)
service.apply_discounts(order)
end
private
def create_shipping_service
ShippingService.new
end
end
Forgetting to override factory methods in subclasses causes runtime errors. Base classes should make factory methods abstract or provide sensible defaults:
# Problematic - easy to forget override
class BaseProcessor
def process(data)
handler = create_handler
handler.handle(data)
end
def create_handler
# Silent failure - returns nil
end
end
# Better - explicit error
class BaseProcessor
def process(data)
handler = create_handler
handler.handle(data)
end
def create_handler
raise NotImplementedError, "#{self.class} must implement create_handler"
end
end
Creating too many factory methods fragments creation logic. Consider whether subclasses actually vary or if parameterized factories would suffice:
# Over-engineered
class EmailProcessor < Processor
def create_parser; EmailParser.new; end
def create_validator; EmailValidator.new; end
def create_formatter; EmailFormatter.new; end
def create_sender; EmailSender.new; end
end
# More maintainable
class EmailProcessor
def initialize
@parser = EmailParser.new
@validator = EmailValidator.new
@formatter = EmailFormatter.new
@sender = EmailSender.new
end
end
Exposing factory methods publicly leaks implementation details. Factory methods should be private unless external code needs to customize creation:
# Problematic - exposes internals
class DataExporter
def export(data)
formatter = create_formatter # Public
formatter.format(data)
end
def create_formatter
JSONFormatter.new
end
end
# Better - private factory
class DataExporter
def export(data)
formatter = create_formatter
formatter.format(data)
end
private
def create_formatter
JSONFormatter.new
end
end
Reference
Pattern Structure
| Component | Responsibility | Implementation |
|---|---|---|
| Product | Defines interface for objects factory creates | Class or module defining common methods |
| Concrete Product | Implements Product interface | Class implementing required methods |
| Creator | Declares factory method returning Product | Base class with abstract or default factory method |
| Concrete Creator | Overrides factory method to return Concrete Product | Subclass implementing factory method |
Factory Method Variations
| Variation | Description | Use Case |
|---|---|---|
| Abstract Factory Method | Raises error if not overridden | Enforce subclass implementation |
| Default Factory Method | Returns default product | Provide base behavior with optional override |
| Parameterized Factory | Accepts arguments to guide creation | Single factory supporting multiple types |
| Lazy Factory | Creates product on first access | Defer expensive creation |
| Cached Factory | Reuses created products | Avoid repeated instantiation |
| Registry Factory | Maps identifiers to products | Dynamic product registration |
Implementation Checklist
| Step | Action | Verification |
|---|---|---|
| 1 | Define Product interface | All products share common methods |
| 2 | Create Concrete Products | Each implements Product interface |
| 3 | Define Creator with factory method | Method returns Product type |
| 4 | Make factory method abstract or provide default | Clear subclass contract |
| 5 | Create Concrete Creators | Each overrides factory method |
| 6 | Use Product through interface | No dependency on concrete types |
Common Mistakes
| Mistake | Problem | Solution |
|---|---|---|
| Factory returns nil | Breaks Liskov Substitution | Raise error or return null object |
| Business logic in factory | Mixes concerns | Keep factories focused on construction |
| Public factory methods | Exposes implementation | Make factory methods private |
| Missing factory override | Runtime errors | Use abstract base or defaults |
| Too many factories | Fragmented logic | Consider composition or parameters |
Decision Matrix
| Scenario | Pattern Choice | Rationale |
|---|---|---|
| Single product type varies by context | Factory Method | Subclass controls variation |
| Multiple related products | Abstract Factory | Create product families |
| Simple product creation | Direct instantiation | Avoid unnecessary abstraction |
| Runtime product selection | Parameterized Factory | Centralized selection logic |
| External product types | Registry Factory | Support plugin architecture |
| Product dependencies vary | Dependency Injection | More flexible than factories |
Ruby-Specific Considerations
| Feature | Usage | Example Pattern |
|---|---|---|
| Modules | Mixin factory methods | include FormatterFactory |
| Blocks | Functional factories | ConfigurableFactory.new { Product.new } |
| Class methods | Stateless factories | Factory.create(type) |
| Method objects | First-class factories | factory = Creator.new(Product) |
| Duck typing | Implicit interfaces | Products respond to expected methods |
| Registry pattern | Hash-based lookup | registry[type].new |