Builder

Builder

The Builder Design Pattern is a creational pattern that separates the construction of complex objects from their representation, allowing the same construction process to create different representations. It enables the construction of objects step by step, facilitating the creation of complex objects with varying configurations. In this article, we'll examine the Builder pattern in depth, its implementation in C#, and provide an example to illustrate its usage.

1. Overview of the Builder Pattern

The Builder pattern is used to construct complex objects by breaking down the construction process into multiple steps. Suppose we have a class with lots of properties. So we need a huge constructor to assign value to all properties. If we don’t need to specify all properties we can pass null or we can overload lots of constructors. Both ways are not good. The best way is using the Builder Pattern. It defines an abstract builder interface that declares methods for building each part of the object, and concrete builder classes implement this interface to provide specific implementations of the construction process. A director class orchestrates the construction process, invoking methods on the builder to create the final object.

I created a solution in my GitHub that contains a project called Builder for this article. You can find it here:

https://github.com/amirdoosti6060/DesignPatterns

2. Structure of the Builder Pattern

  • Builder Interface: Declares methods for building each part of the object.
  • Concrete Builders: Implement the builder interface to provide specific implementations of the construction process.
  • Director: Orchestrates the construction process by invoking methods on the builder.
  • Product: Represents the complex object being constructed.

3. Implementation in C#

Let's demonstrate the Builder pattern with a simple example of building a computer:

// Product: Computer
public class Computer
{
    public string CPU { get; set; }
    public string RAM { get; set; }
    public string GPU { get; set; }
    public string Storage { get; set; }
}

// Builder Interface
public interface IComputerBuilder
{
    void SetCPU(string cpu);
    void SetRAM(string ram);
    void SetGPU(string gpu);
    void SetStorage(string storage);
    Computer Build();
}

// Concrete Builder: ComputerBuilder
public class ComputerBuilder : IComputerBuilder
{
    private Computer computer = new Computer();
    public void SetCPU(string cpu) { computer.CPU = cpu; }
    public void SetRAM(string ram) { computer.RAM = ram; }
    public void SetGPU(string gpu) { computer.GPU = gpu; }
    public void SetStorage(string storage) { computer.Storage = storage; }
    public Computer Build() { return computer; }
}

// Director
public class GamingDirector
{
    private IComputerBuilder builder;

    public GamingDirector(IComputerBuilder builder)
    {
        this.builder = builder;
    }

    public Computer Construct()
    {
        builder.SetCPU("Intel Core i7");
        builder.SetRAM("16GB DDR4");
        builder.SetGPU("NVIDIA GeForce RTX 3080");
        builder.SetStorage("1TB SSD");

        return builder.Build();
    }
}        

In this example Computer is the main product that we want to build but it has lots of parameters so we are using a builder pattern.?

IComputerBuilder is defining an interface for our pattern and ComputerBuilder is the concrete builder class that can create a “Computer” object.?

Note that the builder may pass or set some default parameters of the product (Computer). Also we may design the setting functions so that we can chain the settings like this:

builder.SetCPU("Intel Core i7").SetRAM("16GB DDR4");        

And finally note that it is possible to combine builder and product in one class. In this case a product can create an object of its type but in a step by step manner.

4. Usage of the Builder Pattern

class Program
{

    static void Main(string[] args)
    {
        // Create a director
        GamingDirector director = new GamingDirector(new ComputerBuilder());

        // Construct a gaming computer
        Computer gamingComputer = director.Construct();
    }
}        

5. Benefits of the Builder Pattern

  • Separation of Concerns: Separates the construction of complex objects from their representation, allowing for more maintainable code.
  • Flexibility: Allows the same construction process to create different representations of the object, providing flexibility in object creation.
  • Encapsulation: Encapsulates the construction process within concrete builder classes, promoting loose coupling and modularity.

6. When to Use the Builder Pattern

  • Use the Builder pattern when the construction of complex objects involves multiple steps or variations.
  • Use it when you want to separate the construction process from the representation of the object, allowing for more flexible and maintainable code.

Existing Builder in .NET

While there are not many explicit examples of the Builder pattern in the .NET framework itself, there are several instances where the pattern is implicitly used or where libraries and frameworks employ the concept of builders for object creation and configuration. Here are five examples along with explanations of why they can be considered instances of the Builder pattern:

1. Entity Frameworks DbContextOptionsBuilder

  • Explanation: Entity Framework allows developers to configure database contexts using DbContextOptionsBuilder. This builder class provides methods for configuring various options such as connection strings, database providers, and behavior settings. Developers can use method chaining to fluently configure the options before building the final DbContextOptions object, which is then used to create database contexts.

2. ASP.NET Cores IServiceCollection

  • Explanation: ASP.NET Core uses IServiceCollection as a builder for configuring services in the application's dependency injection container. Developers can use methods like AddTransient, AddScoped, and AddSingleton to register services with different lifetimes and configurations. After configuring services, the IServiceCollection is built into an IServiceProvider that resolves dependencies throughout the application.

3. HttpClientBuilder in ASP.NET Cores AddHttpClient extension method

  • Explanation: ASP.NET Core provides the AddHttpClient extension method to register HttpClient instances with customizable configurations. Under the hood, this method uses a HttpClientBuilder to configure options such as base address, default headers, and HTTP message handlers. Developers can use method chaining to fluently configure the HttpClient before building it and registering it with the dependency injection container.

4. FluentAssertions AssertionOptions

  • Explanation: FluentAssertions is a popular library for fluent assertion syntax in unit tests. It allows developers to configure assertion behavior and options using the AssertionOptions class. The AssertionOptions class provides methods for configuring various options such as assertion behavior, formatting, and customization. Developers can use method chaining to fluently configure the options before building the final AssertionOptions object.

5. Moqs MockRepository

  • Explanation: Moq is a mocking library for creating mock objects in unit tests. It provides a MockRepository class as a builder for creating mock objects with customizable setups and behavior. Developers can use methods like Create and Setup to configure mock objects before building them. The MockRepository ensures that the mock objects are created with the desired configurations and behavior.

Conclusion

In conclusion, the Builder Design Pattern provides a structured and flexible approach to construct complex objects step by step. By following the principles outlined in this article and using the provided example, you can leverage the Builder pattern in your C# applications to create complex objects with varying configurations in a clear and maintainable manner.


#designpattern #builder #csharp #dotnet

要查看或添加评论,请登录

社区洞察

其他会员也浏览了