CrackedRuby CrackedRuby

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