Part 2.2 – Factory Method Design Pattern
Series: Design Patterns in C# and ASP.NET Core
Pattern Type: Creational Design Pattern
Difficulty: ⭐⭐☆☆☆ (Beginner to Intermediate)
Prerequisites: OOP Concepts, Interfaces, Inheritance, Polymorphism
Table of Contents
Introduction
What is Factory Method Pattern?
Why Do We Need Factory Method?
The Problem with
newReal-World Analogy
Factory Method Structure
UML Class Diagram
Components of the Pattern
Step-by-Step C# Example
ASP.NET Core Example
Real-World Use Cases
Advantages
Disadvantages
Factory Method vs Simple Factory
Best Practices
Common Mistakes
Interview Questions
Summary
Introduction
One of the most common practices among beginner developers is creating objects directly using the new keyword.
Payment payment = new CreditCardPayment();
At first glance, this looks perfectly fine. However, as applications grow, direct object creation introduces tight coupling, making the system harder to maintain, extend, and test.
Imagine an e-commerce application that initially supports only Credit Card payments. Later, business requirements change, and the application must support PayPal, UPI, Net Banking, and Cryptocurrency payments.
If every part of the application directly creates payment objects using new, every location must be updated when a new payment type is introduced. This increases maintenance effort and the risk of bugs.
The Factory Method Pattern addresses this by separating object creation from object usage.
What is the Factory Method Pattern?
Definition
The Factory Method Pattern defines an interface (or abstract method) for creating objects, allowing subclasses or specialized factory classes to decide which concrete object to instantiate.
In simple terms, instead of creating objects directly, you ask a factory to create them.
Instead of writing:
new CreditCardPayment();
you write:
factory.CreatePayment();
The client no longer needs to know the concrete class being created.
Why Do We Need the Factory Method Pattern?
Without the Factory Method Pattern:
Business logic is tightly coupled to concrete classes.
Adding new implementations requires modifying existing code.
Testing becomes more difficult because dependencies are hard-coded.
The application violates the Open/Closed Principle (OCP).
With the Factory Method Pattern:
Object creation is centralized.
New implementations can be added with minimal changes.
Client code depends on abstractions rather than concrete implementations.
The system becomes easier to maintain and extend.
The Problem with new
Consider an online shopping application.
public class PaymentProcessor
{
public void Process()
{
CreditCardPayment payment = new CreditCardPayment();
payment.Pay();
}
}
The PaymentProcessor is directly dependent on CreditCardPayment.
If support for PayPal is added, the class must be modified:
public void Process(string type)
{
if(type == "Card")
new CreditCardPayment();
else if(type == "PayPal")
new PaypalPayment();
else if(type == "UPI")
new UpiPayment();
}
This approach quickly becomes cluttered and difficult to maintain.
Real-World Analogy
Imagine ordering coffee at a café.
You don't walk into the kitchen and prepare the coffee yourself.
Instead, you tell the barista what you want.
The barista decides:
Which ingredients to use.
Which machine to operate.
How the coffee is prepared.
You simply receive the finished coffee.
In this analogy:
Customer → Client
Barista → Factory
Coffee → Product
The customer doesn't know how the coffee is made—they only request a type of coffee.
Factory Method Structure
The pattern consists of four main participants:
Product – Defines the interface.
Concrete Product – Implements the product.
Creator (Factory) – Declares the factory method.
Concrete Creator – Implements the factory method to return a specific product.
UML Class Diagram
+----------------------+
| IPayment |
+----------------------+
| + Pay() |
+----------^-----------+
|
+---------------------+---------------------+
| |
+----------------------+ +----------------------+
| CreditCardPayment | | PaypalPayment |
+----------------------+ +----------------------+
| + Pay() | | + Pay() |
+----------------------+ +----------------------+
+----------------------+
| PaymentFactory |
+----------------------+
| + CreatePayment() |
+----------^-----------+
|
+----------------------+----------------------+
| |
+------------------------+ +------------------------+
| CreditCardFactory | | PaypalFactory |
+------------------------+ +------------------------+
| CreatePayment() | | CreatePayment() |
+------------------------+ +------------------------+
Components of the Pattern
Product
Defines the common interface.
public interface IPayment
{
void Pay(decimal amount);
}
Concrete Products
public class CreditCardPayment : IPayment
{
public void Pay(decimal amount)
{
Console.WriteLine($"Paid ₹{amount} using Credit Card.");
}
}
public class PaypalPayment : IPayment
{
public void Pay(decimal amount)
{
Console.WriteLine($"Paid ₹{amount} using PayPal.");
}
}
Creator (Factory)
public abstract class PaymentFactory
{
public abstract IPayment CreatePayment();
}
Concrete Factories
public class CreditCardFactory : PaymentFactory
{
public override IPayment CreatePayment()
{
return new CreditCardPayment();
}
}
public class PaypalFactory : PaymentFactory
{
public override IPayment CreatePayment()
{
return new PaypalPayment();
}
}
Step-by-Step C# Example
Client Code
class Program
{
static void Main()
{
PaymentFactory factory = new CreditCardFactory();
IPayment payment = factory.CreatePayment();
payment.Pay(2500);
}
}
Output
Paid ₹2500 using Credit Card.
If you want to switch to PayPal:
PaymentFactory factory = new PaypalFactory();
No other client code changes are required.
ASP.NET Core Example
In ASP.NET Core, the Factory Method Pattern is often combined with Dependency Injection.
Product Interface
public interface INotificationService
{
void Send(string message);
}
Concrete Products
public class EmailNotificationService : INotificationService
{
public void Send(string message)
{
Console.WriteLine($"Email: {message}");
}
}
public class SmsNotificationService : INotificationService
{
public void Send(string message)
{
Console.WriteLine($"SMS: {message}");
}
}
Factory
public interface INotificationFactory
{
INotificationService Create(string notificationType);
}
public class NotificationFactory : INotificationFactory
{
public INotificationService Create(string notificationType)
{
return notificationType switch
{
"Email" => new EmailNotificationService(),
"SMS" => new SmsNotificationService(),
_ => throw new ArgumentException("Unsupported notification type")
};
}
}
Register Services
builder.Services.AddSingleton<INotificationFactory, NotificationFactory>();
Controller
public class NotificationController : ControllerBase
{
private readonly INotificationFactory _factory;
public NotificationController(INotificationFactory factory)
{
_factory = factory;
}
[HttpPost]
public IActionResult Send(string type, string message)
{
var service = _factory.Create(type);
service.Send(message);
return Ok();
}
}
Real-World Use Cases
The Factory Method Pattern is commonly used when the exact type of object depends on runtime conditions.
Examples include:
Payment gateways (Credit Card, UPI, PayPal, Stripe)
Notification systems (Email, SMS, Push)
Document generators (PDF, Excel, Word)
Database providers (SQL Server, PostgreSQL, MySQL)
Cloud storage providers (Azure Blob Storage, AWS S3, Google Cloud Storage)
Report exporters (CSV, PDF, Excel)
Advantages
Promotes loose coupling by depending on abstractions.
Supports the Open/Closed Principle.
Centralizes object creation logic.
Makes testing easier by allowing mock implementations.
Simplifies adding new product types without changing client code.
Encourages cleaner and more maintainable code.
Disadvantages
Introduces additional classes, increasing the size of the codebase.
Can feel excessive for very small or simple applications.
Requires developers to understand abstraction and inheritance.
Poorly designed factories can become overly complex.
Factory Method vs Simple Factory
| Feature | Simple Factory | Factory Method |
|---|---|---|
| Standard GoF Pattern | ❌ No | ✅ Yes |
| Uses Inheritance | ❌ Usually not | ✅ Yes |
| Uses Polymorphism | Limited | Extensive |
| Extensible | Limited | High |
| Open/Closed Principle | Often violated when adding new products | Better supported |
| Complexity | Lower | Higher |
| Best For | Small applications | Medium to large applications |
Simple Factory centralizes creation in a single class, often using if or switch statements. It's straightforward but can require modification when new product types are added.
Factory Method relies on inheritance and polymorphism. New product types are introduced by creating new factory subclasses rather than modifying existing factory logic, aligning better with the Open/Closed Principle.
Best Practices
Program to interfaces, not concrete implementations.
Keep factories focused on object creation only.
Avoid placing business logic inside factories.
Use Dependency Injection to supply factories where appropriate.
Consider the Abstract Factory Pattern when multiple related objects must be created together.
Common Mistakes
Overusing the Pattern
Not every object needs a factory. For simple data objects or classes with trivial construction, using new directly is often perfectly acceptable.
Mixing Business Logic into Factories
Factories should create objects, not perform business operations.
Returning Concrete Types
Return interfaces or abstract base classes from factory methods whenever possible. This keeps client code decoupled from specific implementations.
Interview Questions
1. What is the Factory Method Pattern?
It is a creational design pattern that defines an interface for creating objects while allowing subclasses or specialized factories to determine which concrete object to instantiate.
2. What problem does the Factory Method Pattern solve?
It removes direct dependencies on concrete classes, making code more maintainable, extensible, and testable.
3. How does the Factory Method Pattern support the Open/Closed Principle?
New product types can be introduced by adding new concrete factories and products without modifying existing client code.
4. What is the difference between Factory Method and Abstract Factory?
Factory Method creates one product through a factory method. Abstract Factory creates families of related products using a factory interface with multiple creation methods.
5. What is the difference between Factory Method and Simple Factory?
A Simple Factory usually uses conditional logic in one class to create products. Factory Method uses inheritance and polymorphism, making it easier to extend without modifying existing code.
6. Where is the Factory Method Pattern commonly used?
Payment processing
Notification services
Database provider selection
Report generation
Cloud storage integration
Plugin architectures
Summary
The Factory Method Pattern is a foundational creational design pattern that separates object creation from object usage. By introducing factories and programming to interfaces, it reduces coupling, improves extensibility, and aligns with SOLID principles—particularly the Open/Closed Principle and Dependency Inversion Principle.
While direct use of new is appropriate in many simple scenarios, Factory Method becomes valuable when object creation depends on runtime conditions, configuration, or evolving business requirements. Combined with ASP.NET Core's Dependency Injection framework, it enables flexible, testable, and maintainable application architectures.
Coming Up Next
In Part 2.3, we'll explore the Abstract Factory Design Pattern, including:
What is the Abstract Factory Pattern?
Factory Method vs. Abstract Factory
Creating families of related objects
UML Class Diagram
Complete C# Console Application
ASP.NET Core implementation
Real-world examples
Advantages and disadvantages
Common mistakes
Interview questions and best practices
You'll see how Abstract Factory builds upon Factory Method to create entire groups of related objects while maintaining consistency across product families.