DEV Community

Cover image for Builder Design Pattern using C#
Satya Biswal
Satya Biswal

Posted on

Builder Design Pattern using C#

Table of content

  • Introduction
  • Intent
  • Participants
  • Example
  • Use Cases

Introduction

The Builder Design Pattern is a Creational design pattern in C# and other object-oriented programming languages.

It is used to construct complex objects step by step, allowing the construction process to vary independently from the actual representation of the object.

This pattern is particularly useful when you have an object with many possible configurations and it's not desirable to have a large number of constructors with different parameter combinations.

Intent:

  • Separate the construction of a complex object from its representation.
  • Allow the same construction process to create different representations.
  • Provide a clear and understandable way to construct an object, especially when it involves a large number of parameters or configuration options.

Participants:

  1. Builder: An abstract interface that defines the steps and operations needed to construct a product.
  2. Concrete Builder: Implementations of the Builder interface, which provide specific methods for building and configuring the product.
  3. Director: This class orchestrates the construction process using a builder to create a product. It's not always necessary, but it can be used to ensure that the construction process follows a specific order.
  4. Product: The final object that's being constructed, representing the complex object.

Example:

Let's consider an example of building a Pizza object using the Builder pattern. We'll have a Pizza class with various properties like size, crust type, toppings, etc., and a PizzaBuilder to create Pizza objects step by step.

// Product
class Pizza
{
    public string Size { get; set; }
    public string CrustType { get; set; }
    public List<string> Toppings { get; set; }

    public void Display()
    {
        Console.WriteLine($"Size: {Size}, Crust: {CrustType}, Toppings: {string.Join(", ", Toppings)}");
    }
}

// Builder
interface IPizzaBuilder
{
    void SetSize(string size);
    void SetCrustType(string crustType);
    void AddTopping(string topping);
    Pizza Build();
}

// Concrete Builder
class MargheritaPizzaBuilder : IPizzaBuilder
{
    private Pizza pizza = new Pizza();

    public void SetSize(string size)
    {
        pizza.Size = size;
    }

    public void SetCrustType(string crustType)
    {
        pizza.CrustType = crustType;
    }

    public void AddTopping(string topping)
    {
        pizza.Toppings.Add(topping);
    }

    public Pizza Build()
    {
        return pizza;
    }
}

// Director (optional)
class PizzaDirector
{
    private IPizzaBuilder builder;

    public PizzaDirector(IPizzaBuilder builder)
    {
        this.builder = builder;
    }

    public Pizza Construct()
    {
        builder.SetSize("Medium");
        builder.SetCrustType("Thin");
        builder.AddTopping("Cheese");
        builder.AddTopping("Tomato Sauce");
        return builder.Build();
    }
}

Enter fullscreen mode Exit fullscreen mode

Usage

var margheritaBuilder = new MargheritaPizzaBuilder();
var pizzaDirector = new PizzaDirector(margheritaBuilder);
var margheritaPizza = pizzaDirector.Construct();

margheritaPizza.Display();
Enter fullscreen mode Exit fullscreen mode

Output

Size: Medium, Crust: Thin, Toppings: Cheese, Tomato Sauce
Enter fullscreen mode Exit fullscreen mode

Use Cases:

  1. Building Complex Objects: The Builder pattern is useful when you need to create complex objects with multiple configuration options. It provides a more readable and maintainable way to construct such objects.
  2. Configuring Immutable Objects: In situations where you want to create immutable objects (objects whose state cannot be changed after creation), the Builder pattern is a good fit. You can build the object step by step and then create an immutable instance.
  3. Variety of Configurations: When you have multiple configurations or variations of an object, using a builder can simplify the creation process and improve code readability.
  4. Fluent Interfaces: Builders often make use of a fluent interface, which allows you to chain method calls together for a more expressive and intuitive configuration process.
  5. Unit Testing: The Builder pattern is helpful in unit testing when you need to create objects for testing purposes. It allows you to create specific instances with different configurations easily.

Top comments (2)

Collapse
 
andreea_m profile image
Andreea M. • Edited

You could try chaining the methods, by having the builder ones return the IPizzaBuilder.
E.g.
public IPizzaBuilder SetSize(string size)
{
pizza.Size = size;
return this;
}

And then you can have something like:


Pizza pizza = builder.AddTopping("Olives").SetCrustType("thin").Build();

Collapse
 
thedevalchemist profile image
Satya Biswal

Absolutely.