Getting your Trinity Audio player ready...
Factory Method Design Pattern

In the world of software architecture, the new keyword is a double-edged sword. Every time you write EmailNotification.new, you’ve made a choice you might regret later. You’ve hard-wired your logic to a specific class, making your code rigid, difficult to test, and prone to breaking when requirements shift.
The Factory Method is the architectural escape hatch. It’s a creational design pattern that allows you to stop worrying about which object you’re creating and start focusing on how you use it.


The Core Concept: Why We Need a “Factory” Logic

Imagine you’re running a global logistics empire. Your software handles “Deliveries.” Early on, everything goes by Truck. Your code is littered with Truck.new.

Then, you expand overseas. Now you need Ships. Suddenly, you’re gutting your entire codebase to swap Truck for Ship.

The Factory Method solves this by creating a specialized “Manager” (the Creator) that handles the messy business of instantiation. Your main code simply says, “Give me a transport vehicle,” and the Factory decides whether it’s a Truck, a Ship, or a Cargo Plane based on the context.

The Power Couple: Creators and Products

ComponentThe Role
The ProductAn interface (or abstract class) that defines what the object can do (e.g., #deliver).
The CreatorThe class that declares the factory_method. It uses the product but doesn’t care which version it gets.

Rails Magic: The Factory Hidden in Polymorphism

If you’ve used Ruby on Rails, you’ve likely been using the Factory Method pattern without even realizing it. The most sophisticated example is Polymorphic Associations.

Rails uses a built-in factory to handle the logic of “Commentable” objects. Instead of you manually checking if a comment belongs to a Post or a Video, Rails’ internal factory method handles the instantiation for you.

The Code Implementation

class Post < ApplicationRecord
  has_many :comments, as: :commentable
end

class Video < ApplicationRecord
  has_many :comments, as: :commentable
end

class Comment < ApplicationRecord
  belongs_to :commentable, polymorphic: true
end

The “Magic” Moment

Look at how the .new method on an association acts as a context-aware factory:

# Scenario A: The Post Context
@post = Post.first
@post.comments.new # The factory knows to set commentable_type: "Post"

# Scenario B: The Video Context
@video = Video.first
@video.comments.new # The SAME method now returns a comment for a "Video"

Taking it Further: Building a Custom Service Factory

While Rails handles associations beautifully, you’ll often need to build your own factories for third-party integrations. Imagine your app needs to send notifications via Twilio, SendGrid, or Slack based on a user’s preference.

The Implementation

class NotificationFactory
  def self.build(provider_type)
    case provider_type
    when :sms   then TwilioService.new
    when :email then SendGridService.new
    when :slack then SlackService.new
    else
      raise "Unknown provider: #{provider_type}"
    end
  end
end

In your main application logic, you now have a single, clean line:

NotificationFactory.build(user.pref).deliver(message)


The Strategic Advantage: Why the Factory Wins

If you’re still wondering if the extra layer is worth it, consider these three pillars of “clean” engineering:

1. The Open/Closed Principle (OCP)

Your code should be open for extension but closed for modification. With a Factory, adding a new provider (like “WhatsApp”) doesn’t require you to go into your business logic and add another if/else block. You simply create a new service class and add one line to your Factory. The rest of your app stays untouched.

2. Single Responsibility Principle (SRP)

The class that uses the notification shouldn’t be the same class that constructs it. By moving the “how-to-build” logic into a Factory, your Service Objects or Controllers stay slim and focused on their actual jobs.

3. The Testing Edge: Mocking with Ease

One of the most overlooked benefits is how it simplifies Testing and Dependency Injection.

When your code is tightly coupled with TwilioService.new, writing a unit test becomes a nightmare—you don’t want your test suite sending real SMS messages every time you run your specs.

By using a Factory, you can “inject” a mock version of your service during testing:

# In your test environment, you can stub the factory
allow(NotificationFactory).to receive(:build).and_return(MockNotificationService.new)

# Now, your code runs safely without hitting external APIs
user.notify!("Hello World") 

Knowing the Limits: When NOT to Use a Factory

As powerful as the Factory Method is, it isn’t a silver bullet. Over-engineering is just as dangerous as tight coupling. You should probably skip the Factory when:

  • The Logic is Static: If your application will only ever use one specific class and the initialization never changes, adding a factory is just “boilerplate noise.”
  • Simple Instantiation: If you are just creating a simple data object with no conditional logic or external dependencies, a direct .new is perfectly fine.

Rule of thumb: If you don’t anticipate needing a second “Product” type, keep it simple. Don’t build a factory for a single-product town.

Conclusion: Engineering for Change

The Factory Method isn’t about adding layers of bureaucracy to your code; it’s about buying insurance for your future self. By delegating the responsibility of “who gets created” to a specialized method, you transform your application from a rigid monolith into a flexible system that can pivot as fast as your requirements do.

When you master this pattern, you stop writing code that is “done” and start writing code that is extensible. The next time you reach for the new keyword, pause and ask if a Factory should be making that decision for you. Your future self—and your teammates—will thank you for the foresight.


Further Reading & References

Explore more articles and insights on software engineering and technology at Rently Engineering.

Leave a Reply

Login with