Factory Design Pattern in C#: A Guide to Flexible Object Creation

Factory Design Pattern in C#: A Guide to Flexible Object Creation

21 Sep 2024
Intermediate
127K Views
17 min read
Learn with an interactive course and practical hands-on labs

⭐ .NET Design Patterns Course: Design Patterns in C# Online Training

Factory Design Pattern

Factory Design Pattern is a basic concept in C#'s object-oriented programming. It enables you to generate objects without defining the specific type of object that will be created. This pattern encapsulates object generation, making it easier and more manageable. Using a factory method or factory class allows you to abstract away the instantiation process, resulting in a more modular and scalable solution.

In this design pattern tutorial, we'll look at the Factory Design Pattern, explain what it is and when to use it, and provide examples to demonstrate its application. Let us begin by examining the following question: "What is the Factory Design Pattern?"

What is the Factory Method Pattern?

  • The Factory Method Pattern is a design pattern that provides an interface for creating objects while allowing subclasses to determine the type of objects to be made. Bypassing the instantiation process to subclasses encourages loose coupling.
  • Consider a logistics company that uses a variety of transportation methods, such as trucks, ships, and planes.
  • The company may utilize a factory method to generate the proper transportation object depending on the individual requirements, allowing for simple extension or modification of transportation kinds without affecting the core logistics code.

Understanding the Problem of not using the Factory Design Pattern

When you do not use the Factory Design Pattern, you may encounter various issues:
  • Tight coupling: This occurs when your code is tightly bound to specific classes, making it impossible to change or extend the types of objects created without modifying the current code. This can result in decreased flexibility and more maintenance effort.
  • Code Duplication: Without a factory, object creation code is frequently duplicated across the program. This redundancy can cause inconsistencies and problems in the code, making it more challenging to manage.
  • Difficulty in Testing: Testing components based on specific implementations might be tricky. Using a factory allows you to simply change out implementations for testing, making your code more testable and reusable.
  • Scalability Issues: As your application expands, managing object creation directly in client code can become inefficient. The Factory Design Pattern centralizes object generation, making it more scalable and maintainable.
The Factory Design Pattern addresses these concerns, resulting in more manageable, versatile, and scalable code.

Example without using the Factory Pattern in C#

Example

using System;

public abstract class Document
{
    public abstract void Open();
}

public class WordDocument : Document
{
    public override void Open()
    {
        Console.WriteLine("Opening a Word document.");
    }
}

public class PDFDocument : Document
{
    public override void Open()
    {
        Console.WriteLine("Opening a PDF document.");
    }
}

public class DocumentManager
{
    public void OpenDocument(string type)
    {
        Document doc;

        if (type == "Word")
        {
            doc = new WordDocument();
        }
        else if (type == "PDF")
        {
            doc = new PDFDocument();
        }
        else
        {
            throw new ArgumentException("Invalid document type");
        }

        doc.Open();
    }
}

class Program
{
    static void Main()
    {
        DocumentManager manager = new DocumentManager();

        manager.OpenDocument("Word"); // Output: Opening a Word document.
        manager.OpenDocument("PDF");  // Output: Opening a PDF document.
    }
}

In this code, the DocumentManager class uses conditional logic to create WordDocument or PDFDocument objects based on the document type specified. This technique ties DocumentManager to specific document types and necessitates modification of the OpenDocument function whenever new document types are added, making it less flexible and more challenging to maintain.

After Implementing the Factory Design Pattern in C#

Example

using System;

// Abstract Product
public abstract class Document
{
    public abstract void Open();
}

// Concrete Product 1
public class WordDocument : Document
{
    public override void Open()
    {
        Console.WriteLine("Opening a Word document.");
    }
}

// Concrete Product 2
public class PDFDocument : Document
{
    public override void Open()
    {
        Console.WriteLine("Opening a PDF document.");
    }
}

// Factory
public static class DocumentFactory
{
    public static Document CreateDocument(string type)
    {
        switch (type)
        {
            case "Word":
                return new WordDocument();
            case "PDF":
                return new PDFDocument();
            default:
                throw new ArgumentException("Invalid document type");
        }
    }
}

// Client Code
class Program
{
    static void Main()
    {
        try
        {
            // Use the factory to create a WordDocument
            Document wordDoc = DocumentFactory.CreateDocument("Word");
            wordDoc.Open(); // Output: Opening a Word document.

            // Use the factory to create a PDFDocument
            Document pdfDoc = DocumentFactory.CreateDocument("PDF");
            pdfDoc.Open(); // Output: Opening a PDF document.

            // Attempting to create an invalid document type
            Document invalidDoc = DocumentFactory.CreateDocument("Excel");
            invalidDoc.Open(); // This line will throw an exception
        }
        catch (ArgumentException ex)
        {
            Console.WriteLine(ex.Message); // Output: Invalid document type
        }
    }
}

In this example, the DocumentFactory class centralizes the generation of Document objects, allowing the client code in the Main method to request documents without knowing which classes are involved. This technique simplifies object generation, increases flexibility by eliminating reliance on specific implementations, and makes document types easier to manage and extend.

Factory Method Pattern - UML Diagram & Implementation

The UML class diagram for the implementation of the factory method design pattern is given below:

Factory Method Pattern

The classes, interfaces, and objects in the above UML class diagram are as follows:

Product
  • Role: Interface
  • Purpose: Specify a standard interface for factory-created items. It assures that all concrete products follow the same set of processes.
ConcreteProduct
  • Role: Class
  • Purpose: This class implements the Product interface and its specialized implementation. It defines the product's concrete behavior and attributes.
Creator
  • Role: Abstract Class.
  • Purpose: Declares a factory method that returns a Product instance. It may also define additional methods that employ Product objects, but it does not explain how to build them. This course serves as a framework for developing products.
ConcreteCreator
  • Role: Subclass of Creator.
  • Purpose: Uses the factory technique to create customized Product instances. Defines the actual process of creating goods and guarantees that the correct Product type is instantiated. This class provides a concrete implementation for product generation.

Here's a more extensive example that demonstrates the relationships

interface Product
{

}

class ConcreteProductA : Product
{
}

class ConcreteProductB : Product
{
}

abstract class Creator
{
 public abstract Product FactoryMethod(string type);
}

class ConcreteCreator : Creator
{
 public override Product FactoryMethod(string type)
 {
 switch (type)
 {
 case "A": return new ConcreteProductA();
 case "B": return new ConcreteProductB();
 default: throw new ArgumentException("Invalid type", "type");
 }
 }
}

This code demonstrates the Factory Method design pattern, in which a ConcreteCreator class determines which concrete Product type to construct based on input, separating object production from consumption.

Understanding Factory Method Pattern using a real-time example

Factory Method Pattern

The classes, interfaces, and objects in the above class diagram can be identified as follows:

  1. IFactory - This interface defines the way to create objects. It declares the factory method, which yields an IVehicle object (or something similar).

  2. Scooter & Bike - These classes reflect the products that the factory method generates. They use the IVehicle interface (or something similar) to identify individual vehicle kinds, such as scooters and bicycles.

  3. VehicleFactory - This is an abstract class or interface that declares the factory method, which returns an IVehicle. It may also include other methods that work with IVehicle objects, but it does not indicate which type of vehicle will be built.

  4. ConcreteVehicleFactory - These are concrete classes that use the factory approach to produce specific items. For example, ScooterFactory and BikeFactory are concrete creators that override the factory method to create and return a Scooter or Bike object, respectively.

C# - Sample Code

using System;
namespace Factory
{
 /// <summary>
 /// The 'Product' interface
 /// </summary>
 public interface IFactory
 {
 void Drive(int miles);
 }

 /// <summary>
 /// A 'ConcreteProduct' class
 /// </summary>
 public class Scooter : IFactory
 {
 public void Drive(int miles)
 {
 Console.WriteLine("Drive the Scooter : " + miles.ToString() + "km");
 }
 }

 /// <summary>
 /// A 'ConcreteProduct' class
 /// </summary>
 public class Bike : IFactory
 {
 public void Drive(int miles)
 {
 Console.WriteLine("Drive the Bike : " + miles.ToString() + "km");
 }
 }

 /// <summary>
 /// The Creator Abstract Class
 /// </summary>
 public abstract class VehicleFactory
 {
 public abstract IFactory GetVehicle(string Vehicle);

 }

 /// <summary>
 /// A 'ConcreteCreator' class
 /// </summary>
 public class ConcreteVehicleFactory : VehicleFactory
 {
 public override IFactory GetVehicle(string Vehicle)
 {
 switch (Vehicle)
 {
 case "Scooter":
 return new Scooter();
 case "Bike":
 return new Bike();
 default:
 throw new ApplicationException(string.Format("Vehicle '{0}' cannot be created", Vehicle));
 }
 }

 }
 
 /// <summary>
 /// Factory Pattern Demo
 /// </summary>
 class Program
 {
 static void Main(string[] args)
 {
 VehicleFactory factory = new ConcreteVehicleFactory();

 IFactory scooter = factory.GetVehicle("Scooter");
 scooter.Drive(10);

 IFactory bike = factory.GetVehicle("Bike");
 bike.Drive(20);

 Console.ReadKey();

 }
 }
}

This code includes a Vehicle Factory that wraps vehicle production. This allows clients to access alternative vehicles (scooters or Bikes) without knowing their specific classes, promoting flexibility and loose coupling.

Output

Drive the Scooter : 10km
Drive the Bike : 20km
Read More Articles Related to Design Pattern
Summary

The factory method design pattern provides a flexible and centralized approach to object production. It separates clients from the creation process, allowing dynamic object instantiation based on runtime requirements. This pattern allows for clearer, more maintainable code and promotes loose coupling, making it an invaluable tool for developers. Also, consider our .NET design patterns training for a better understanding of other design patterns concepts.

FAQs

The Factory Method pattern addresses the issue of creating objects without claiming the specific class of object that will be created, allowing subclasses to change the type of objects created. It encourages loose coupling by eliminating reliance on individual classes, which improves code flexibility and maintenance.

The Factory Design Pattern in C# is used to produce objects in a way that encapsulates their creation logic, enhancing flexibility and scalability by allowing subclasses to change the type of objects created.

The Factory Design Pattern is most useful when a class cannot predict the type of objects it will need to construct, allowing for dynamic instantiation while also fostering code decoupling and scalability.

The disadvantages of the Factory pattern include increasing complexity as a result of the addition of new classes and interfaces, as well as potential difficulties in organizing and comprehending the codebase as the number of products and creators increases.

The Factory Design Pattern's primary purpose is to establish an interface for creating objects that allow subclasses to change the type of objects that are created, fostering flexibility and eliminating reliance on specific classes.
Share Article
About Author
Shailendra Chauhan (Microsoft MVP, Founder & CEO at Scholarhat by DotNetTricks)

Shailendra Chauhan, Founder and CEO of ScholarHat by DotNetTricks, is a renowned expert in System Design, Software Architecture, Azure Cloud, .NET, Angular, React, Node.js, Microservices, DevOps, and Cross-Platform Mobile App Development. His skill set extends into emerging fields like Data Science, Python, Azure AI/ML, and Generative AI, making him a well-rounded expert who bridges traditional development frameworks with cutting-edge advancements. Recognized as a Microsoft Most Valuable Professional (MVP) for an impressive 9 consecutive years (2016–2024), he has consistently demonstrated excellence in delivering impactful solutions and inspiring learners.

Shailendra’s unique, hands-on training programs and bestselling books have empowered thousands of professionals to excel in their careers and crack tough interviews. A visionary leader, he continues to revolutionize technology education with his innovative approach.
Accept cookies & close this