Mastering Builder Design Pattern in C# for Beginners

Özkan ARDİL
8 min readJan 16, 2024

--

Builder design pattern in c#

Using the builder design pattern in C#, you can create complex objects that have multiple parts.

In this article, we’ll;

  • Explore builder design pattern from class diagrams to real-world code examples,
  • Unveil its advantages,
  • Dissect the disadvantages,
  • Ensure it aligns with the SOLID principles.

By the end, you’ll be the master builder design pattern, enhancing your software with finesse.

For builder design pattern, we can say that it is important for the creation process of an object to be independent of these parts, which means the construction process doesn’t have to take into account the assembly of these parts.

Additionally, you should be able to utilize the same construction method to create different representations of the objects.

Class Diagram of Builder Design Pattern

You will find the class diagram below.

Class diagram of Builder Design Pattern

Elements

  • Product class is the complex object under consideration.
  • Builder interface is a contract implemented by ConcreteBuilder classes.
  • ConcreteBuilder class implements the Builder interface to construct and assemble the parts of a Product object. It is responsible for creating the internal representations of the products, defining the creation process and assembly mechanisms.
  • Director class takes charge of using the Builder interface to create the ultimate object.

Solution Explorer

You will find the high-level structure of the parts of the program.

Solution explorer of the Builder Design Pattern sample project

Implementation

This example has the following participants: IBuilder, BusinessComputer, GamingComputer, Product, and Director. IBuilder is used to create parts of the Product object, where Product represents the complex object under construction.

BusinessComputer and GamingComputer are the concrete implementations of the IBuilder interface. They implement the IBuilder interface.

That’s why they need to supply the body for these methods:

  • SetBrand(),
  • AddRam(),
  • AddCpu(),
  • AddGraphicCard(),
  • EndOperations(),
  • GetComputer().

The first five methods are straightforward; they are used to perform some operation at the beginning, set the case of the computer, add a number of features onto it, and perform an operation at the end, respectively.

GetComputer() returns the ultimate product.

Finally, the Director is responsible for constructing the final representation of these products using the IBuilder interface.

Notice that Director is calling the same Construct() method to create different types of computers.

So, let’s create the code for the elements of the sample project.

IBuilder.cs Interface.

 public interface IBuilder
{
void SetBrand();
void AddRam();
void AddCpu();
void AddGraphicCard();
void EndOperations();
Product GetComputer();
}

BusinessComputer.cs class implements the IBuilder Interface.

 internal class BusinessComputer : IBuilder
{
private string brandName;
private Product product;

public BusinessComputer(string brand)
{
product = new Product();
this.brandName = brand;
}

public void SetBrand()
{
product.Add(string.Format("Business computer brand name :{0}", this.brandName));
}

public void AddRam()
{
product.Add("32GB RAM");
}

public void AddCpu()
{
product.Add("Intel Core 2 Duo CPU");
}

public void AddGraphicCard()
{
product.Add("Embedded GPU");
}

public void EndOperations()
{
product.Add("Business computer is ready to be used.");
}

public Product GetComputer()
{
return product;
}

}

GamingComputer.cs class implements the IBuilder Interface.

 internal class GamingComputer : IBuilder
{
private string brandName;
private Product product;

public GamingComputer(string brand)
{
product = new Product();
this.brandName = brand;
}

public void SetBrand()
{
product.Add(string.Format("Gaming computer brand name :{0}", this.brandName));
}

public void AddRam()
{
product.Add("128GB RAM");
}

public void AddCpu()
{
product.Add("Intel Core 8 CPU");
}

public void AddGraphicCard()
{
product.Add("External GPU");
}

public void EndOperations()
{
product.Add("Caming computer is ready to be used.");
}

public Product GetComputer()
{
return product;
}

}

Product.cs class

 public class Product
{
// We can use any data structure that you prefer e.g.List<string> etc.
private LinkedList<string> parts;

public Product()
{
parts = new LinkedList<string>();
}
public void Add(string part)
{
//Adding parts
parts.AddLast(part);
}

public void Show()
{
Console.WriteLine("\nProduct completed as below :");
foreach (string part in parts)
Console.WriteLine(part);
Console.WriteLine("\n");
}
}

Director.cs class.

 internal class Director
{
IBuilder? _builder;

public void Construct(IBuilder builder)
{
_builder = builder;
_builder.SetBrand();
_builder.AddRam();
_builder.AddCpu();
_builder.AddGraphicCard();
_builder.EndOperations();
}
}

So, I put them together within the program.cs class.

Console.WriteLine("***Builder Design Pattern Demo***\n");

Director director = new Director();

// Build business computer
IBuilder businessComputer = new BusinessComputer("HP");
director.Construct(businessComputer);
Product productBusinessComputer = businessComputer.GetComputer();
productBusinessComputer.Show();

// Build gaming computer
IBuilder gamingComputer = new GamingComputer("Monster");
director.Construct(gamingComputer);
Product productGamingComputer = gamingComputer.GetComputer();
productGamingComputer.Show();

You’ll find the output below.

The output of the Builder Design Pattern sample project

I preferred to use Interface to create IBuilder. It is possible to use abstract class instead. It is up to the need of the project.

An abstract class offers centralized or default behaviour with the option for default implementations, while an interface, defines rules or contracts for what should be done without specifying how.

Interfaces are ideal for implementing multiple inheritance.

However, adding a new method to an interface means implementing it across all instances, requiring concrete implementations in each place. In contrast, an abstract class allows seamless addition of new methods with default implementations, ensuring existing code runs smoothly.

Therefore, depending on the requirements and structure of the project, the developer should decide whether to use an interface or an abstract class.

Source Code

You can access the source code of the project on my Design Patterns in C# GitHub repository.

If you can give the repository a star and share the article, you will support me in reaching more people.

Advantages of Builder Design Pattern

Separation of Concerns: It separates the construction of a complex object from its representation, allowing the same construction process to create different representations.

Readable and Fluent Interface: It provides a clear, readable way to construct objects step by step, often using method chaining, resulting in a fluent interface that enhances code readability.

Flexibility in Object Creation: Builders allow the creation of different configurations or variations of an object by varying the order of construction steps or parameters.

Encapsulation of Construction Logic: The pattern encapsulates the construction logic within the builder class, shielding the client from the complexities of object construction.

Control over Immutability: It facilitates the creation of immutable objects by constructing them in a step-by-step manner and then returning the fully constructed immutable object.

Reduced Complexity: Especially useful when dealing with objects with a large number of optional parameters, it simplifies object creation by providing defaults and only requiring the specification of the desired parameters.

Disadvantages of the Builder Design Pattern

Increased Code Overhead: Implementing the builder pattern involves creating additional classes (builder classes) which can lead to increased code complexity, especially for smaller projects or simple object constructions.

Potential for Object Duplication: If not implemented carefully, there might be a risk of creating multiple builder classes for similar object constructions, leading to code duplication and maintenance issues.

Requires Understanding of Pattern: Developers unfamiliar with the Builder pattern might find it challenging to comprehend its implementation, potentially leading to errors or misuse.

Possible Performance Impact: In some cases, especially with highly optimized systems or performance-critical applications, the additional layers introduced by the Builder pattern could have a minor impact on performance due to the extra method calls involved.

Complexity of Simple Objects: For simpler object creation scenarios with a limited number of parameters, applying the Builder pattern might introduce unnecessary complexity, making the code harder to follow.

Overall, while the Builder Design Pattern enhances code readability, promotes flexibility in object creation, and encapsulates the construction process, making it a valuable pattern in software development, its use should be weighed against the specific needs of the project to determine if the advantages outweigh these potential drawbacks.

Builder Design Pattern and SOLID Principles in C#

Single Responsibility Principle (SRP): The Builder pattern adheres to SRP by separating the construction of complex objects from their representation. The Director class orchestrates the building process using the Builder, ensuring that each class has a single responsibility. Builders handle the step-by-step construction of objects, maintaining a single focus on the construction logic. This segregation keeps each class focused on its specific task, promoting maintainability.

Open/Closed Principle (OCP): The Builder pattern supports the OCP by allowing the addition of new types of objects or variations without altering existing client code. By creating new concrete builders or extending existing ones, new object configurations can be introduced without modifying the existing code base, thus ensuring the code remains open for extension but closed for modification.

Liskov Substitution Principle (LSP): The Builder pattern doesn’t directly impact LSP, as it’s more related to inheritance and polymorphism. However, since builders focus on constructing objects through a consistent interface, they contribute to creating objects that can be used interchangeably through a common construction process, indirectly supporting LSP.

Interface Segregation Principle (ISP): The Builder pattern doesn’t directly relate to the ISP, as it doesn’t focus on interfaces in the traditional sense. However, it helps segregate the construction process from the object representation, ensuring that clients interact with a clear, specific interface for building objects. This separation can indirectly support ISP by providing tailored interfaces for building different types of objects.

Dependency Inversion Principle (DIP): The Builder pattern can contribute to DIP by allowing the construction process to depend on abstractions rather than concrete implementations. Clients depend on the Builder interface, abstracting the construction process and enabling flexibility in the types of objects built. This facilitates the injection of different builder implementations, promoting the inversion of dependencies.

Conclusion

As we conclude our exploration of the Builder Design Pattern in C#, it’s evident that it is a versatile and valuable tool in your software development arsenal.

With an understanding of class diagrams, code samples, advantages, and considerations, I hope you’ve gained the expertise to employ Builder Design Pattern effectively.

Keep coding, keep designing, and keep mastering the art of C# development.

--

--

Özkan ARDİL

.NET C# JS and Angular dev with 8+ yrs exp, self-taught & passionate web developer. Sharing tips & experiences in C# and web dev.