DEV Community

Cover image for Understanding the Factory Design Pattern in C# with Real-World Examples
Sebastian Van Rooyen
Sebastian Van Rooyen

Posted on

Understanding the Factory Design Pattern in C# with Real-World Examples

The Factory Design Pattern is a powerful concept in object-oriented design that promotes the creation of objects without specifying the exact class of the object that will be created. It’s particularly useful when dealing with complex systems that require different types of objects, such as payment providers in an enterprise application.

In this article, we’ll explore the Factory Design Pattern in C# with relatable examples and explain how it can be applied to real-world scenarios, like integrating different payment providers into a payment processing system.

What is the Factory Design Pattern?

The Factory Design Pattern defines an interface for creating objects but allows subclasses to alter the type of objects that will be created. This pattern delegates the responsibility of instantiating objects to a factory class, which makes the code more flexible and easier to manage.

Key Benefits:

  • Decoupling: It decouples the code that uses the objects from the code that creates the objects.
  • Flexibility: It makes it easier to introduce new types of objects without changing the code that uses them.

Basic Structure

Here’s a basic structure of the Factory Design Pattern:

  1. Product: Defines the interface for the objects the factory method creates.
  2. ConcreteProduct: Implements the Product interface.
  3. Creator: Declares the factory method, which returns an object of type Product. It may also define a default implementation.
  4. ConcreteCreator: Overrides the factory method to return an instance of a ConcreteProduct.

Example: Payment Providers

Imagine you’re building an e-commerce application that supports multiple payment providers, such as PayPal and Stripe. The Factory Design Pattern helps manage these different providers in a clean and scalable way.

Here’s how you can implement it:

1. Define the Product Interface

public interface IPaymentProvider
{
    void ProcessPayment(decimal amount);
}
Enter fullscreen mode Exit fullscreen mode

2. Implement Concrete Products

public class PayPalProvider : IPaymentProvider
{
    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} payment through PayPal.");
    }
}

public class StripeProvider : IPaymentProvider
{
    public void ProcessPayment(decimal amount)
    {
        Console.WriteLine($"Processing ${amount} payment through Stripe.");
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Create the Factory Interface

public interface IPaymentProviderFactory
{
    IPaymentProvider CreatePaymentProvider();
}
Enter fullscreen mode Exit fullscreen mode

4. Implement Concrete Factories

public class PayPalFactory : IPaymentProviderFactory
{
    public IPaymentProvider CreatePaymentProvider()
    {
        return new PayPalProvider();
    }
}

public class StripeFactory : IPaymentProviderFactory
{
    public IPaymentProvider CreatePaymentProvider()
    {
        return new StripeProvider();
    }
}
Enter fullscreen mode Exit fullscreen mode

5. Using the Factory

In your application, you can use the factory to create instances of payment providers without directly coupling to their implementations:

class Program
{
    static void Main(string[] args)
    {
        IPaymentProviderFactory factory = new PayPalFactory(); // or new StripeFactory();
        IPaymentProvider provider = factory.CreatePaymentProvider();
        provider.ProcessPayment(100.00m);
    }
}
Enter fullscreen mode Exit fullscreen mode

In this example, the Program class does not need to know about the specific details of PayPalProvider or StripeProvider. It only interacts with IPaymentProvider through the factory, making it easy to switch providers or add new ones without changing the client code.

Conclusion

The Factory Design Pattern is a valuable tool in designing flexible and maintainable software. By using factories, you can create a system where objects are instantiated based on some logic or configuration, without directly coupling the creation logic to the code that uses the objects. This pattern is particularly useful in scenarios like integrating different payment providers or handling various types of services where the specific implementation details can change over time.

Feel free to experiment with this pattern in your own projects and see how it can help you manage complex object creation in a clean and efficient way!

Top comments (0)