Abstract Factory Pattern: Designing Families of Related Objects in C#

Introduction

Our previous article, "The Factory Method Pattern: Simplifying Object Creation", simplifies object creation by delegating it to subclasses, promoting flexibility and maintainability in the code.

In object-oriented design, creating families of related objects can be challenging, especially when the exact types of objects to be created are not known until runtime. The Abstract Factory Pattern provides a solution by offering an interface for creating families of related or dependent objects without specifying their concrete classes. This pattern is an extension of the Factory Method Pattern and is particularly useful in scenarios where you need to ensure that a set of objects is created together, maintaining a consistent configuration.

Understanding the Abstract Factory Pattern

The Abstract Factory Pattern defines an interface for creating objects, but it allows subclasses to produce different families of products. Each family of products is designed to work together, ensuring that the created objects are compatible with one another. This pattern promotes consistency and decouples the client code from the specific classes of objects it needs to use.

Key Components of Abstract Factory Pattern

  • AbstractFactory: Declares the creation methods for each type of product.
  • ConcreteFactory: Implements the creation methods to produce specific products.
  • AbstractProduct: Declares the interface for a type of product.
  • ConcreteProduct: Implements the AbstractProduct interface.
  • Client: Only the interfaces declared by the AbstractFactory and AbstractProduct classes are used.

Example in C#

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

1. Define the Product Interfaces Under Document Folder

IDocument.cs

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

Explanation

This interface defines methods (Open() and Close()) that all document types (Word, PDF, etc.) must implement.

IToolbar.cs

namespace Abstract_Factory_Pattern_Demo.Documents
{
    public interface IToolbar
    {
        void Render();
    }
}

Explanation

This interface defines the Render() method, which is implemented by toolbars related to each document type (e.g., Word toolbar, PDF toolbar).

2. Create Concrete Products Under Document Folder

WordDocument.cs

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

Explanation

Implements the IDocument interface. When Open() is called, it simulates opening a Word document. Similarly, Close() closes the Word document.

PdfDocument.cs

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

Explanation

Implements the IDocument interface for PDFs. Provides the behavior for opening and closing PDF documents.

WordToolbar.cs

namespace Abstract_Factory_Pattern_Demo.Documents
{
    public class WordToolbar : IToolbar
    {
        public void Render() => Console.WriteLine("Rendering Word Document Toolbar.");
    }
}

Explanation

Implements the IToolbar interface. When Render() is called, it simulates rendering the toolbar for a Word document.

PdfToolbar.cs

namespace Abstract_Factory_Pattern_Demo.Documents
{
    public class PdfToolbar : IToolbar
    {
        public void Render() => Console.WriteLine("Rendering PDF Document Toolbar.");
    }
}

Explanation

Implements the IToolbar interface for PDF. Provides the behavior for rendering the toolbar for PDF documents.

3. Define the Abstract Factory Interface Under the Factories Folder

IGUIFactory.cs

namespace Abstract_Factory_Pattern_Demo.Factories
{
    using Abstract_Factory_Pattern_Demo.Documents;
    public interface IGUIFactory
    {
        IDocument CreateDocument();
        IToolbar CreateToolbar();
    }
}

Explanation

  • This interface defines the methods for creating related objects: CreateDocument() and CreateToolbar().
  • The idea is that a concrete factory will create both a document and a toolbar of the same family (Word or PDF).

4. Create Concrete Factories Under the Factories Folder

WordFactory.cs

namespace Abstract_Factory_Pattern_Demo.Factories
{
    using Abstract_Factory_Pattern_Demo.Documents;
    public class WordFactory : IGUIFactory
    {
        public IDocument CreateDocument() => new WordDocument();
        public IToolbar CreateToolbar() => new WordToolbar();
    }
}

Explanation

This factory creates Word-related products. It implements CreateDocument() to return a WordDocument and CreateToolbar() to return a WordToolbar.

PdfFactory.cs

namespace Abstract_Factory_Pattern_Demo.Factories
{
    using Abstract_Factory_Pattern_Demo.Documents;
    public class PdfFactory : IGUIFactory
    {
        public IDocument CreateDocument() => new PdfDocument();
        public IToolbar CreateToolbar() => new PdfToolbar();
    }
}

Explanation

This factory creates PDF-related products. It provides methods to create both PdfDocument and PdfToolbar instances.

5. Client Code

Program.cs

using Abstract_Factory_Pattern_Demo.Factories;

class Program
{
    static void Main(string[] args)
    {
        IGUIFactory factory;
        Console.WriteLine("Enter the type of document to create (Word/PDF):");
        var input = Console.ReadLine();
        switch (input.ToLower())
        {
            case "word":
                factory = new WordFactory();
                break;
            case "pdf":
                factory = new PdfFactory();
                break;
            default:
                throw new ArgumentException("Invalid document type");
        }
        var document = factory.CreateDocument();
        var toolbar = factory.CreateToolbar();
        document.Open();
        toolbar.Render();
        document.Close();
    }
}

Explanation

  • The client selects the type of document (Word or PDF) to create.
  • Based on user input, the corresponding factory (WordFactory or PDFFactory) is instantiated.
  • The client then creates both the document and toolbar using the factory and finally calls Open(), Render(), and Close() on the respective products.
  • The client is unaware of the specific classes being created, as it only interacts with the abstract IDocument and IToolbar interfaces.

File Structure For the Above Example.

File structure

Output

Output

Visual Studio

Real-World Use Cases

The Abstract Factory Pattern is useful in several scenarios.

  • UI Design: For creating different UI elements (buttons, checkboxes) that match a particular theme or style.
  • Configuration Systems: For generating configurations for different environments (e.g., development, testing, production) that require compatible settings.
  • Data Access Layers: These are used to create different types of data access objects based on database types (e.g., SQL, NoSQL).

Benefits of Abstract Factory Pattern

  • Consistency: Ensures that related objects are created together and are compatible with one another.
  • Flexibility: Allows for easy extension of product families without changing existing client code.
  • Decoupling: Reduces dependency on concrete classes, promoting a more modular design.

Summary

The Abstract Factory Pattern provides a robust mechanism for creating families of related objects while ensuring that the objects are compatible with one another. This pattern enhances consistency and flexibility, making it easier to manage complex object creation scenarios.

Next Steps

In the next article, we will explore "Singleton Pattern: Ensuring a Single Instance in .NET Core". This pattern ensures that a class has only one instance and provides a global point of access to it. We will explore its implementation in .NET Core and discuss its use cases and benefits.

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

Thank you, and happy coding.

Up Next
    Ebook Download
    View all
    Learn
    View all