DEV Community

Chaitali Khangar
Chaitali Khangar

Posted on

The Factory Method Pattern: The Secret Behind Scalable Code

A few years ago, I worked on a growing application that started small but expanded faster than we expected.

Each new feature introduced a new way to create objects - notifications, reports, services - and every time, we added another if-else block.

At first, it was fine.

Then one day, a minor change broke three modules because a single creation method was edited in multiple files.

That's when I realized the problem wasn't in the logic - it was in the design.

We weren't managing object creation; we were repeating it.
That's when I discovered design patterns.

Why Design Patterns?

As applications evolve, developers often face the same structural problems repeatedly.

How do you create objects without duplicating logic?
How do you extend functionality without breaking existing code?

Instead of solving these problems from scratch every time, developers started identifying and documenting reusable approaches.

That led to the Gang of Four (GoF) book - Design Patterns: Elements of Reusable Object-Oriented Software (1994).

A design pattern isn't a library or a framework.
It's a reusable way to think about recurring design challenges.

The Real Problem

Imagine you're building a notification service.
You start with a simple design:

if type == :email
  Notification::Email.new
elsif type == :sms
  Notification::Sms.new
else
  Notification::Push.new
end
Enter fullscreen mode Exit fullscreen mode

It works.

But as the system grows, more notification types appear - Slack, WhatsApp, internal alerts.

Each new type means another line, another edit, another potential mistake.

Your code is now doing two things:
Running business logic.
Deciding which class to create.

This is exactly the kind of problem the Factory Method Pattern solves.

What Is the Factory Method Pattern?
The Factory Method Pattern is a creational design pattern that provides an interface for creating objects without specifying their concrete classes.

In other words, you tell the system what you need, and it decides how to build it.

How It Works
Let's refactor the earlier example.

class NotificationFactory
  def self.build(type)
    klass_name = "Notifications::#{type.to_s.camelize}"

    if Object.const_defined?(klass_name)
      klass_name.constantize.new
    else
      raise "Unknown notification type: #{type}"
    end
  end
end
module Notification
  class Email
    def send_message
      puts "Sending email..."
    end
  end

  class Sms
    def send_message
      puts "Sending SMS..."
    end
  end

  class Push
    def send_message
      puts "Sending push notification..."
    end
  end
end

# When you call
notification = NotificationFactory.build(:email)
notification.send_message
# Output: Sending email...

notification = NotificationFactory.build(:whatsapp)
notification.send_message
# Output: Unknown notification type: whatsapp
Enter fullscreen mode Exit fullscreen mode

Now you have implemented a Factory Method pattern.

In the future, if a new type Slack is added,
You just define the class inside Notification- no change in the factory, no code broken.

Visual Flow
Here's how this example works conceptually:

This is the essence of the Factory Method Pattern - 
The client doesn't care which class was created.
It just asks the factory, and the right object is returned.

How Major Frameworks Use It

The Factory Method Pattern is everywhere - even if you don't notice it.
Rails: 
When you call validates :email, presence: true, Rails dynamically decides whether to use PresenceValidator, LengthValidator, or FormatValidator.

React:
React.createElement(type) determines which component to render at runtime.

When (and When Not) to Use the Factory Method Pattern

Before applying it, here's a quick decision framework to help you decide whether the Factory Method Pattern is the right choice.

Use the Factory Method Pattern when:

  • You have multiple related object types.

  • You expect to add more types in the future.

  • You want to centralize and simplify object creation logic.

Avoid it when:

  • The number of types is fixed and small.

  • Simplicity matters more than scalability.

Now you've seen how replacing scattered if-else statements with intentional creation logic brings clarity and structure.

You're not just improving readability - you're building for the future.

Till next time - build systems that grow, but never crumble.

ruby #rails #designpatterns #factorymethod

Top comments (0)