DEV Community

Cover image for Understanding the Factory Method Pattern in C#
Daniel Azevedo
Daniel Azevedo

Posted on

Understanding the Factory Method Pattern in C#

Hey everyone!

Today, I want to share some thoughts on a design pattern that’s incredibly useful when you’re dealing with object creation: the Factory Method Pattern. It’s a pattern I’ve come to appreciate, especially when you need more flexibility and want to avoid hard-coding object instantiation all over your codebase.

Let’s dive into what it is, why it’s valuable, and how you can implement it in C#.

What is the Factory Method Pattern?

The Factory Method Pattern is a creational pattern that provides an interface for creating objects in a super flexible way. Instead of directly calling a constructor (new), the Factory Method pattern lets subclasses decide which class to instantiate. This is especially handy when you don’t want your code to depend on specific classes but rather on abstractions.

Why Use the Factory Method Pattern?

Here’s where the Factory Method Pattern really shines:

  1. Flexibility in Object Creation: You can define how objects are created without hard-coding the exact class. It gives you flexibility to change the object creation logic without touching the client code.

  2. Promotes Loose Coupling: The client code only interacts with interfaces or abstract classes, so it doesn't need to know the specific class being instantiated.

  3. Extensibility: If you need to add new types or classes, you can easily extend the code without modifying the existing logic, making your code easier to maintain.

A Practical Example in C

Let’s look at a scenario where the Factory Method Pattern can help. Imagine you’re building an application where users can generate different types of reports—PDF, Excel, or Word. You don’t want to hard-code the creation of these report types in your service logic.

Without Factory Method

Here’s what the code might look like without using the Factory Method Pattern:

public class ReportService
{
    public void GenerateReport(string type)
    {
        if (type == "PDF")
        {
            var report = new PdfReport();
            report.Generate();
        }
        else if (type == "Excel")
        {
            var report = new ExcelReport();
            report.Generate();
        }
        else if (type == "Word")
        {
            var report = new WordReport();
            report.Generate();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach works, but it’s not scalable. Each time you add a new report type, you need to modify the ReportService class. It’s also tightly coupled to specific classes, which is not ideal.

Implementing Factory Method

Let’s apply the Factory Method Pattern to clean this up:

// Step 1: Create an abstract Product class
public abstract class Report
{
    public abstract void Generate();
}

// Step 2: Implement concrete products
public class PdfReport : Report
{
    public override void Generate()
    {
        Console.WriteLine("Generating PDF Report");
    }
}

public class ExcelReport : Report
{
    public override void Generate()
    {
        Console.WriteLine("Generating Excel Report");
    }
}

public class WordReport : Report
{
    public override void Generate()
    {
        Console.WriteLine("Generating Word Report");
    }
}

// Step 3: Create the Factory Method
public abstract class ReportFactory
{
    public abstract Report CreateReport();
}

public class PdfReportFactory : ReportFactory
{
    public override Report CreateReport()
    {
        return new PdfReport();
    }
}

public class ExcelReportFactory : ReportFactory
{
    public override Report CreateReport()
    {
        return new ExcelReport();
    }
}

public class WordReportFactory : ReportFactory
{
    public override Report CreateReport()
    {
        return new WordReport();
    }
}

// Step 4: Use the Factory Method in your service
public class ReportService
{
    private readonly ReportFactory _factory;

    public ReportService(ReportFactory factory)
    {
        _factory = factory;
    }

    public void GenerateReport()
    {
        var report = _factory.CreateReport();
        report.Generate();
    }
}
Enter fullscreen mode Exit fullscreen mode

What’s Happening Here?

  • Abstract Class (Report): Defines the common behavior that all report types must implement.
  • Concrete Classes (PdfReport, ExcelReport, WordReport): Implement the specifics of each report type.
  • Factory Method (ReportFactory): An abstract class with a method (CreateReport) that defines how the objects are created.
  • Concrete Factories: Each report type gets its own factory, responsible for creating that specific type of report.
  • Report Service: Now, the service is flexible and doesn’t need to know what type of report it’s dealing with—it just calls the factory.

Why is This Better?

  • Scalability: Adding a new report type? No problem. Just create a new report class and a corresponding factory. You don’t need to touch the existing code.
  • Loose Coupling: The service depends on abstractions (interfaces and abstract classes) rather than concrete classes. This makes the code more maintainable.
  • Cleaner Code: You centralize the object creation in the factory classes, so the service doesn’t have to worry about how to instantiate the reports.

When Should You Use the Factory Method Pattern?

  • When you have a complex object creation process that shouldn’t be exposed directly to the client.
  • When your application needs flexibility to switch between different product types without modifying the client code.
  • When you want to promote extensibility without breaking existing code.

Wrapping Up

The Factory Method Pattern is a powerful tool when you need flexibility in object creation. It’s especially useful when you have multiple product types, like in the report generation example above. With the Factory Method, your code becomes more scalable, maintainable, and easier to extend over time.

Have you used the Factory Method Pattern in your projects? What challenges or benefits have you encountered? Let’s talk about it in the comments below!

Happy coding!!!!

Top comments (0)