When building applications, creating an object is rarely as simple as calling User.new.
Different objects may require different initialization logic, validation, external API calls, or configuration. If every class is responsible for deciding what object to create, this logic quickly becomes scattered throughout the codebase.
Factory patterns solve this problem by separating object creation from object usage, making the code easier to maintain and extend.
Let's look at the two most common approaches: Simple Factory and Factory Method.
Simple Factory
it is not actually a design pattern by the book but it is commonly used.
it has a very basic idea of encapsulating the objects creation from it’s usage
it has a class ‘Simple Factory’ that contains the logic of creating objects , that factory is used in a “client” to create the objects then use it
Instead of allowing clients to instantiate objects directly, a single factory class becomes responsible for deciding which object should be created.
Structure
A Simple Factory consists of:
- A factory class that contains all the object creation logic.
- A single factory method that returns different objects based on some condition.
- A client that uses the factory instead of creating objects directly.
a naive implementation of simple factory in ruby
class UserSimpleFactory
def self.create_user(type, user_args)
case type
when 'admin'
Admin.create(name: user_args[:name], email: user_args[:email], password: user_args[:password])
when 'customer'
Customer.create(name: user_args[:name], email: user_args[:email], password: user_args[:password])
when 'guest'
Guest.create(name: user_args[:name], country: user_args[:country])
else
raise 'INVALID_TYPE'
end
end
end
class RegisterHandler
def create_user(params)
user = UserSimpleFactory.create_user(params[:type], params[:user])
end
def register(params)
user = create_user(params)
"Hello #{user.name}"
end
end
Advantages
- Centralizes object creation.
- Keeps clients from depending on concrete classes.
- Easy to understand and implement.
The Problem
This works well while you only have a few product types.
However, every time a new user type is introduced, the factory needs another when branch.
Eventually the factory becomes a large conditional statement responsible for creating every possible object.
This violates the Open/Closed Principle because adding a new product requires modifying an existing class.
at that time you might think to extract parts of this method into different classes and voilà you get a factory method pattern
Factory Method
The Gang of Four defines Factory Method as:
Define an interface for creating an object, but let subclasses decide which class to instantiate.
this pattern lets a class defer instantiation to subclass. and encapsulates the instantation of concrete types
The parent class defines the workflow, while each subclass decides which concrete object should be created.
This removes the need for long case statements and replaces them with polymorphism.
Structure
- Abstract creator : it gives you an interface with method(THE factory method) to create object(product), any other method on this class operates on the object returned by the factory method
- Concrete creator : is a subclass that implement the factory method
- Abstract product; interface(parent) of all products that different concrete creators return
- Concrete product must implement the same interface so that the factories can refere to the interface not the concrete classes
this pattern decouples the implementation of the product from its use
class RegisterHandler
def create_user(user_args)
raise NotImplementedError
end
def register(params)
user = create_user(params)
"Hello #{user.name}"
end
end
class AdminRegister < RegisterHandler
def create_user(user_args)
Admin.create(name: user_args[:name], email: user_args[:email], password: user_args[:password])
end
end
class CustomerRegister < RegisterHandler
def create_user(user_args)
Customer.create(name: user_args[:name], email: user_args[:email], password: user_args[:password])
end
end
class GuestRegister < RegisterHandler
def create_user(user_args)
Guest.create(name: user_args[:name], country: user_args[:country])
end
end
Notice that the register method is implemented only once.
The only thing subclasses customize is how the user is created.
The registration workflow remains unchanged regardless of the user type.
Advantages
- Eliminates large conditional statements.
- Follows the Open/Closed Principle.
- Makes it easy to introduce new product types.
- Uses polymorphism instead of branching logic.
- Keeps creation logic isolated inside dedicated creator classes.
The trade-off is that it introduces more classes, which can feel unnecessary for small applications.
Did you know that Rails framework is full of Factory patterns? 👀
Example: Action Mailer
Every mailer inherits from ApplicationMailer
When you define
class UserMailer < ApplicationMailer
def welcome(user)
mail(to:user.email)
end
end
The framework handles the workflow of building and delivering an email.
Your subclass supplies the message-specific details.
Example: Active Job adapters
You write:
class SendEmailJob < ApplicationJob
end
Then configure:
config.active_job.queue_adapter = :solid_queue
or
config.active_job.queue_adapter = :sidekiq
Each adapter creates its own queue implementation.
Rails defines the workflow.
The adapter subclasses provide the implementation
Key Takeaways
A Simple Factory centralizes object creation using conditional logic. It's a pragmatic solution for smaller applications but becomes harder to maintain as the number of products grows.
Factory Method takes a different approach by replacing those conditionals with polymorphism. Instead of modifying one factory every time a new product is added, you create a new creator class that knows how to build that product.
A good rule of thumb is this:
If your factory keeps getting longer every time you introduce a new product, it's probably time to replace it with Factory Method.
for a full example in ruby check this https://github.com/ShroukAbozeid/design-pattern/tree/main/factory

Top comments (0)