Jump to content

Factory Method and Abstract Factory

From Knowledge Base

Factory Method and Abstract Factory

Error creating thumbnail: Unable to save thumbnail to destination

The Factory Method and Abstract Factory are creational design patterns used to create objects and provide a way to create objects without specifying the exact class of object that will be created, allowing for flexibility and extensibility.

Use Cases

  • Database Access: When the system needs to be independent of how its data is accessed and stored.
  • Cross-Platform Applications: Applications that need to create objects that are platform-specific.
  • UI Toolkits: Different toolkits for different operating systems but with the same usage interface.

How It Works

  • Factory Method Pattern: A single factory class provides a method to create and return new instances of various classes within a single product hierarchy.
  • Abstract Factory Pattern: An interface declares a set of methods for creating each of the abstract products. Concrete factories implement this interface and produce a family of products.

Factory Method Pattern

The Factory Method pattern defines an interface for creating an object but lets subclasses alter the type of objects that will be created. It encapsulates the object creation process in a separate method, providing an abstract class or interface with one or more concrete implementations.

Benefits

  • Flexibility: Clients can work with different and new derived classes without changing their code.
  • Encapsulation: Clients are unaware of the actual concrete classes they are using, as creation is encapsulated within the Factory.
  • Ease of Integration: It can be easier to introduce new object types into a system because the Factory acts as a single point of change.

Example case

Let's consider a scenario where we need to create different types of documents, such as PDFs and Word documents. We can define a Document interface and create concrete document classes that implement this interface. However, the choice of which concrete document class to create can vary.

Implementation

 // Define the Document interface
 interface Document {
     void Open();
     void Save();
 }

 // Concrete implementation of PDF Document
 class PDFDocument : Document {
     public void Open() { /* Implementation for opening a PDF document */ }
     public void Save() { /* Implementation for saving a PDF document */ }
 }

 // Concrete implementation of Word Document
 class WordDocument : Document {
     public void Open() { /* Implementation for opening a Word document */ }
     public void Save() { /* Implementation for saving a Word document */ }
 }

 // Factory interface for creating documents
 interface DocumentFactory {
     Document CreateDocument();
 }

 // Concrete factory for creating PDF documents
 class PDFDocumentFactory : DocumentFactory {
     public Document CreateDocument()
     {
         // do some magic stuff like calling QuestPDF for .NET
         return new PDFDocument();
     }
 }

 // Concrete factory for creating Word documents
 class WordDocumentFactory : DocumentFactory {
     public Document CreateDocument()
     {
         // do some magic stuff like calling GrapeCity Documents for .NET (costs money)
         return new WordDocument();
     }
 }

Putting it together:

class Program {
    static void Main(string[] args) {
        // Client code
        DocumentFactory pdfFactory = new PDFDocumentFactory();
        Document pdfDocument = pdfFactory.CreateDocument();

        DocumentFactory wordFactory = new WordDocumentFactory();
        Document wordDocument = wordFactory.CreateDocument();

        // Usage
        pdfDocument.Open();
        pdfDocument.Save();

        wordDocument.Open();
        wordDocument.Save();
    }
}
  • The Document interface is defined with methods for opening and saving documents.
  • Concrete implementations for PDFDocument and WordDocument classes are provided, both adhering to the Document interface.
  • A DocumentFactory interface is established for creating documents.
  • Concrete factories, namely PDFDocumentFactory and WordDocumentFactory, implement the DocumentFactory interface to create instances of PDF and Word documents.

Abstract Factory Pattern

The Abstract Factory pattern is an extension of the Factory Method pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. It focuses on creating multiple related objects that work together seamlessly.

Benefits

  • Consistency Among Products: Ensures that products that are meant to be used together are compatible.
  • Separation of Concerns: Separates the responsibility for the overall product creation from the product's actual creation logic.
  • Interchangeability: Families of product objects can be swapped out easily at runtime due to the common interfaces.

Example case

Building upon the scenario of creating different types of documents, we now extend our requirements to include creating user interfaces that are specific to the documents being created. We want to ensure that the UI components are compatible with the chosen document type. For example, PDF documents should have UI components that match the PDF format, and Word documents should also have corresponding UI components.

Implementation

 // Abstract Product A interface
 interface Button
 {
     void Render();
 }

 // Concrete Product A for PDF documents
 class PDFButton : Button
 {
     public void Render()
     {
         Console.WriteLine("Rendering a PDF-style button");
     }
 }

 // Concrete Product A for Word documents
 class WordButton : Button
 {
     public void Render()
     {
         Console.WriteLine("Rendering a Word-style button");
     }
 }

 // Abstract Product B interface
 interface Checkbox
 {
     void Render();
 }

 // Concrete Product B for PDF documents
 class PDFCheckbox : Checkbox
 {
     public void Render()
     {
         Console.WriteLine("Rendering a PDF-style checkbox");
     }
 }

 // Concrete Product B for Word documents
 class WordCheckbox : Checkbox
 {
     public void Render()
     {
         Console.WriteLine("Rendering a Word-style checkbox");
     }
 }

 // Abstract Factory interface
 interface UIAbstractFactory
 {
     Button CreateButton();
     Checkbox CreateCheckbox();
 }

 // Concrete Factory for PDF UI
 class PDFUIFactory : UIAbstractFactory
 {
     public Button CreateButton()
     {
         return new PDFButton();
     }

     public Checkbox CreateCheckbox()
     {
         return new PDFCheckbox();
     }
 }

 // Concrete Factory for Word UI
 class WordUIFactory : UIAbstractFactory
 {
     public Button CreateButton()
     {
         return new WordButton();
     }

     public Checkbox CreateCheckbox()
     {
         return new WordCheckbox();
     }
 }

Putting it all together:

class Program
{
    static void Main(string[] args)
    {
        // Client code
        // Document creation
        DocumentFactory pdfFactory = new PDFDocumentFactory();
        Document pdfDocument = pdfFactory.CreateDocument();

        DocumentFactory wordFactory = new WordDocumentFactory();
        Document wordDocument = wordFactory.CreateDocument();

        // UI creation using Abstract Factory
        UIAbstractFactory pdfUIFactory = new PDFUIFactory();
        Button pdfButton = pdfUIFactory.CreateButton();
        Checkbox pdfCheckbox = pdfUIFactory.CreateCheckbox();

        UIAbstractFactory wordUIFactory = new WordUIFactory();
        Button wordButton = wordUIFactory.CreateButton();
        Checkbox wordCheckbox = wordUIFactory.CreateCheckbox();

        // Usage
        // Document usage
        pdfDocument.Open();
        pdfDocument.Save();

        wordDocument.Open();
        wordDocument.Save();

        // UI component rendering
        pdfButton.Render();
        pdfCheckbox.Render();

        wordButton.Render();
        wordCheckbox.Render();
    }
}

We first create documents using the Factory Method pattern, and then we create UI components that are specific to the document type using the Abstract Factory pattern. This ensures that the UI components are compatible with the chosen document type, which is exactly the kind of problem the Abstract Factory pattern is designed to solve.

The Abstract Factory pattern complements the Factory Method pattern, allowing you to create entire families of related objects with ease.

Practical Usage in C#

  • Entity Framework Core (EF Core): The Abstract Factory pattern is utilized to create different DbContext instances for various database providers, such as SQL Server and PostgreSQL. This enables applications to switch between different database engines without significant changes to the codebase.
  • NUnit Testing Framework: The Abstract Factory pattern can be employed within NUnit to create test data factories. Abstract factory interfaces are defined to generate test objects, with specific implementations for different types of tests. This strategy is particularly useful for organizing test cases and managing setup complexity, ensuring that tests remain clear and maintainable.
  • Dependency Injection Environments: The pattern is adept at constructing different sets of dependencies based on the application's environment, such as development, production, or testing. It facilitates the configuration of distinct data repositories or services tailored for various stages of application deployment and testing.

The Abstract Factory pattern can be applied to maintain flexibility and manage dependencies in various aspects of software development.