Factory VS Builder in C#

Introduction:

When it comes to software design, choosing the right pattern can have a significant impact on the overall quality and maintainability of your code. Two commonly used patterns in object-oriented programming are the Factory and Builder patterns. Although they might seem similar at first glance, they serve different purposes and have distinct implementations. In this article, we’ll compare the Factory and Builder patterns in C#, discussing their similarities, differences, and best use cases.

Factory Pattern:

The Factory pattern, as the name suggests, is responsible for creating objects. It encapsulates the object instantiation logic and provides an interface for creating related objects without exposing the instantiation process to the client code. The Factory pattern is especially useful when you need to create objects based on certain conditions or configurations.

Here’s an example illustrating the Factory pattern in C#:

// Product class
class Car
{
    public string Brand { get; set; }
    public string Model { get; set; }
}

// Abstract Factory class
abstract class CarFactory
{
    public abstract Car CreateCar();
}

// Concrete Factory class
class SportsCarFactory : CarFactory
{
    public override Car CreateCar()
    {
        return new Car { Brand = "Ferrari", Model = "488 GTB" };
    }
}

// Client code
class Program
{
    static void Main()
    {
        CarFactory factory = new SportsCarFactory();
        Car sportsCar = factory.CreateCar();

        Console.WriteLine($"Brand: {sportsCar.Brand}, Model: {sportsCar.Model}");
    }
}

In the above example, we have a Product class called Car representing the object to be created. The abstract class CarFactory defines the interface for creating cars, while the concrete factory SportsCarFactory implements the logic to create a specific type of car (in this case, a sports car). The client code uses the factory to create a sports car instance.

Builder Pattern:

The Builder pattern focuses on constructing complex objects step by step. It allows you to create objects by separating the construction logic from their representation, enabling the same construction process to create different representations. This pattern is particularly helpful when dealing with objects that have a large number of properties or complex initialization requirements.

Let’s examine a simple implementation of the Builder pattern in C#:

// Product class
class Pizza
{
    public string Dough { get; set; }
    public string Sauce { get; set; }
    public List<string> Toppings { get; set; }
}

// Builder interface
interface IPizzaBuilder
{
    void BuildDough();
    void BuildSauce();
    void AddToppings();
    Pizza GetPizza();
}

// Concrete builder
class MargheritaPizzaBuilder : IPizzaBuilder
{
    private Pizza _pizza;

    public MargheritaPizzaBuilder()
    {
        _pizza = new Pizza();
    }

    public void BuildDough()
    {
        _pizza.Dough = "Thin Crust";
    }

    public void BuildSauce()
    {
        _pizza.Sauce = "Tomato";
    }

    public void AddToppings()
    {
        _pizza.Toppings = new List<string> { "Cheese", "Basil" };
    }

    public Pizza GetPizza()
    {
        return _pizza;
    }
}

// Director class
class PizzaDirector
{
    private IPizzaBuilder _builder;

    public PizzaDirector(IPizzaBuilder builder)
    {
        _builder = builder;
    }

    public void ConstructPizza()
    {
        _builder.BuildDough();
        _builder.BuildSauce();
        _builder.AddToppings();
    }
}

// Client code
class Program
{
    static void Main()
    {
        IPizzaBuilder builder = new MargheritaPizzaBuilder();
        PizzaDirector director = new PizzaDirector(builder);

        director.ConstructPizza();
        Pizza margheritaPizza = builder.GetPizza();

        Console.WriteLine($"Dough: {margheritaPizza.Dough}, Sauce: {margheritaPizza.Sauce}, Toppings: {string.Join(", ", margheritaPizza.Toppings)}");
    }
}

In the above example, we have a Product class called Pizza representing the object to be constructed. The IPizzaBuilder interface defines the steps needed to build a pizza, while the concrete builder MargheritaPizzaBuilder implements the specific construction process for a margherita pizza. The PizzaDirector class directs the builder to perform the necessary steps, and ultimately, the client code obtains the constructed pizza.

Conclusion:

Both the Factory and Builder patterns play crucial roles in software design. The Factory pattern focuses on object creation, providing an interface to create related objects without exposing the implementation details. On the other hand, the Builder pattern separates the construction process from the object representation, enabling step-by-step construction of complex objects.

Understanding the differences and use cases of these patterns will help you design flexible, maintainable, and scalable software solutions in C#. Whether you need to create objects based on certain conditions or construct complex objects, the Factory and Builder patterns offer valuable tools to enhance your software architecture.

References:

  • Microsoft Docs: Factory Method Design Pattern – https://docs.microsoft.com/en-us/dotnet/standard/design-patterns/factory-method
  • Microsoft Docs: Builder Design Pattern – https://docs.microsoft.com/en-us/dotnet/standard/design-patterns/builder