Factory Method Pattern: Simplifying Object Creation in C#

Introduction

In our previous article, "Categorizing Design Patterns: Creational, Structural, and Behavioral", we categorized design patterns into three main types: Creational, Structural, and Behavioral. We provided an overview of each category, including their purposes and real-world examples.

In this article, we will focus on The Factory Method Pattern. The Factory Method Pattern is a creational design pattern that provides a way to delegate the responsibility of creating objects to subclasses. This pattern simplifies object creation by decoupling the client code from the specific classes that need to be instantiated, making the code more flexible and maintainable.

Understanding the Factory Method Pattern

The Factory Method Pattern defines an interface for creating an object but allows subclasses to decide which class to instantiate. This approach helps reduce tight coupling between the client code and the concrete classes, enhancing flexibility and scalability.

Key Components

  • Product: The interface or abstract class that defines the type of object the factory method will create.
  • ConcreteProduct: The concrete implementation of the Product interface.
  • Creator: The abstract class or interface that declares the factory method, which returns a Product object.
  • ConcreteCreator: The subclass that implements the factory method to return an instance of a ConcreteProduct

Example in C#

Let's illustrate the Factory Method Pattern with a practical example involving document creation. Create two folders in your project Documents and Factories.

1. Define the Product Interface Under Document Folder

IDocument.cs

namespace Factory_Method_Pattern_Demo.Documents
{
    public interface IDocument
    {
        void Open();
        void Close();
    }
}

Explanation

  • This interface defines two methods: Open() and Close().
  • Any document class that implements this interface will provide its own implementation for opening and closing documents.
  • The purpose of the interface is to define the structure for different types of documents (e.g., Word and PDF) that will be created by the factories.

2. Implement Concrete Products Class under document folder

WordDocument.cs

namespace Factory_Method_Pattern_Demo.Documents
{
    public class WordDocument : IDocument
    {
        public void Open() => Console.WriteLine("Opening Word Document.");
        public void Close() => Console.WriteLine("Closing Word Document.");
    }
}

Explanation

  • This class implements the IDocument interface and provides the functionality specific to a Word document.
  • When Open() is called, it outputs a message indicating that a Word document is being opened.
  • When Close() is called, it outputs a message indicating that the Word document is being closed.

PdfDocument.cs

namespace Factory_Method_Pattern_Demo.Documents
{
    public class PdfDocument : IDocument
    {
        public void Open() => Console.WriteLine("Opening PDF Document.");
        public void Close() => Console.WriteLine("Closing PDF Document.");
    }
}

Explanation

  • This class is similar to WordDocument, but it is specific to PDF documents.
  • It provides its own implementation of Open() and Close(), which are responsible for opening and closing PDF documents.

3. Create the Creator Base Class: Abstract Factory Under the Factories Folder

DocumentFactory.cs

namespace Factory_Method_Pattern_Demo.Factories
{
    using Factory_Method_Pattern_Demo.Documents;

    public abstract class DocumentFactory
    {
        public abstract IDocument CreateDocument();
        public void OpenDocument()
        {
            var document = CreateDocument();
            document.Open();
        }
        public void CloseDocument()
        {
            var document = CreateDocument();
            document.Close();
        }
    }
}

Explanation

  • This is an abstract class that defines the factory method CreateDocument(), which must be implemented by concrete factories.
  • The OpenDocument() and CloseDocument() methods use the CreateDocument() factory method to create a document object (either Word or PDF) and then call Open() or Close() on that object.
  • The abstract factory hides the instantiation logic, allowing subclasses to decide the concrete type of document to create.

4. Implement Concrete Products Class under the Factories Folder

WordDocumentFactory.cs

namespace Factory_Method_Pattern_Demo.Factories
{
    using Factory_Method_Pattern_Demo.Documents;
    public class WordDocumentFactory : DocumentFactory
    {
        public override IDocument CreateDocument() => new WordDocument();
    }
}

Explanation

  • This concrete factory class extends DocumentFactory and overrides the CreateDocument() method to return a WordDocument object.
  • This factory is responsible for creating Word documents without the client knowing the specific details of the Word document creation process.

PdfDocumentFactory.cs

namespace Factory_Method_Pattern_Demo.Factories
{
    using Factory_Method_Pattern_Demo.Documents;

    public class PdfDocumentFactory : DocumentFactory
    {
        public override IDocument CreateDocument() => new PdfDocument();
    }
}

Explanation:

  • This concrete factory class is responsible for creating PdfDocument objects.
  • It follows the same structure as the WordDocumentFactory but returns a PdfDocument when CreateDocument() is called.

5. Client Code

Program. cs

using Factory_Method_Pattern_Demo.Factories;

class Program
{
    static void Main(string[] args)
    {
        DocumentFactory factory;
        Console.WriteLine("Enter the type of document to create (Word/PDF):");
        var input = Console.ReadLine();
        switch (input.ToLower())
        {
            case "word":
                factory = new WordDocumentFactory();
                break;
            case "pdf":
                factory = new PdfDocumentFactory();
                break;
            default:
                throw new ArgumentException("Invalid document type");
        }
        factory.OpenDocument();
        factory.CloseDocument();
    }
}

Explanation

  • This is the client code that interacts with the factories.
  • Based on user input (Word or PDF), it selects the appropriate factory (WordDocumentFactory or PdfDocumentFactory) to create the correct type of document.
  • The client doesn't need to know the details of how each document type is created. The Factory Method Pattern ensures that the instantiation logic is encapsulated within the factory classes.
  • After the factory is chosen, the program calls OpenDocument() and CloseDocument() methods, which use the factory method to create and manipulate the document objects.

File Structure For the Above Example.

File structure

Output

Output

Visual Studio

Real-World Use Cases

The Factory Method Pattern is widely used in various scenarios.

  • GUI Libraries: For creating different UI components such as buttons, dialogs, and windows depending on the platform.
  • Logging Systems: For creating different types of loggers (e.g., file logger, console logger) based on configuration settings.
  • Database Connections: For managing connections to various types of databases based on the application's requirements.

Benefits

  • Flexibility: Allows for new types of products to be introduced without modifying existing code.
  • Encapsulation: Encapsulates the object creation logic, resulting in cleaner and more maintainable code.
  • Scalability: Facilitates the addition of new product types with minimal changes to the existing codebase.

Summary

The Factory Method Pattern simplifies object creation by delegating it to subclasses, thus promoting flexibility, encapsulation, and scalability. This pattern is particularly useful when dealing with complex object-creation processes and can significantly enhance the maintainability of the code.

Next Steps

In the next article, we will explore "Abstract Factory Pattern: Designing Families of Related Objects". This pattern extends the Factory Method Pattern to create families of related objects, and we will discuss its components, practical implementations, and real-world applications to illustrate its use in .NET Core projects.
Feel free to adjust or expand upon this content as needed for your specific audience and context!

If you find this article valuable, please consider liking it and sharing your thoughts in the comments.

Thank you, and happy coding.