Type Creational
Purpose Separate the construction of a complex object from its representation so that the same construction process can create different representations.

Description

The Builder pattern separates the construction of a complex object from its representation. It is based on the collaboration between a Director and one or more Builder implementations. Any number of concrete builders can conform to a common Builder interface, and each may be used by the Director to construct a Product according to a particular specification.

The builder supplies the individual parts of the product step by step, under the director's control, until construction is complete. Different concrete builders can follow the same construction process while producing different representations or assemblies of the product.

In this way, the client can rely on a consistent construction process without being coupled to the details of how the product is assembled internally. The completed product is returned only when the building process is finished. The Builder pattern is primarily concerned with how a complex object is constructed.


Overview

Builder
Builder An interface defining what can be built.
Director Defines the sequence of operations used to construct a Product.
ConcreteBuilder Implements the Builder interface and constructs the parts of the Product.
Product The complex object being constructed.
Client A class that calls a Director to construct a Product using a Builder.

Implementation

In this example, we'll create a product that translates from English into Chinese or French. The product will work with HTML or text. The various parts include interpreters, parsers, and renderers.

We'll just create two builders to keep the code concise.

// Parts

public interface IParser
{
    string Parse(string text);
}

public class TextParser : IParser
{
    public string Parse(string text)
        => text.Trim().ToLowerInvariant();
}

public class HtmlParser : IParser
{
    public string Parse(string text)
    {
        text = text.Trim().ToLowerInvariant();
        
        if (text.StartsWith("<p>") && text.EndsWith("</p>"))
        {
            return text[3..^4];
        }

        return "";
    }
}

public interface IInterpreter
{
    string Interpret(string text);
}

public class ChineseInterpreter : IInterpreter
{
    public string Interpret(string text) 
        => text switch
        {
            "hello" => "你好",
            "world" => "世界",
            _ => "不知道"
        };
}

public class FrenchInterpreter : IInterpreter
{
    public string Interpret(string text) 
        => text switch
        {
            "hello" => "bonjour",
            "world" => "monde",
            _ => "je ne sais pas"
        };
}

public interface IRenderer
{
    void Render(string text);
}

public class TextRenderer : IRenderer
{
    public void Render(string text) 
        => Console.WriteLine(text);
}

public class HtmlRenderer : IRenderer
{
    public void Render(string text) 
        => Console.WriteLine($"<p>{text}</p>");
}

// Product

public class EnglishTranslator
{
    public IParser Parser { get; set; }
    
    public IInterpreter Interpreter { get; set; }
    
    public IRenderer Renderer { get; set; }

    public void Translate(string english)
    {
        var parsed = Parser.Parse(english);
        var translation = Interpreter.Interpret(parsed);
        Renderer.Render(translation);
    }
}

// Builder

public interface IBuilder
{
    void BuildParser();
    void BuildInterpreter();
    void BuildRenderer();
    
    EnglishTranslator GetResult();
}

// Concrete Builders

public class EnglishChineseTextTranslatorBuilder : IBuilder
{
    private readonly EnglishTranslator _translator = new();
    
    public void BuildParser()  
        => _translator.Parser = new TextParser();

    public void BuildInterpreter()
        => _translator.Interpreter = new ChineseInterpreter();

    public void BuildRenderer()
        => _translator.Renderer = new TextRenderer();
    
    public EnglishTranslator GetResult() 
        => _translator;
}

public class EnglishFrenchHtmlTranslatorBuilder : IBuilder
{
    private readonly EnglishTranslator _translator = new();
    
    public void BuildParser()  
        => _translator.Parser = new HtmlParser();

    public void BuildInterpreter()
        => _translator.Interpreter = new FrenchInterpreter();

    public void BuildRenderer()
        => _translator.Renderer = new HtmlRenderer();
    
    public EnglishTranslator GetResult() 
        => _translator;
}

// Director

public class Director(IBuilder builder)
{
    public void Construct()
    {
        builder.BuildParser();
        builder.BuildInterpreter();
        builder.BuildRenderer();
    }
}

// Demonstration

public static class BuilderDemo
{
    public static void Execute()
    {
        // english to chinese
        
        var chineseTranslatorBuilder = new EnglishChineseTextTranslatorBuilder();
        
        var director = new Director(chineseTranslatorBuilder);
        director.Construct();
        
        var translator = chineseTranslatorBuilder.GetResult();
        translator.Translate("Hello");
        
        // english to french
        
        var frenchTranslatorBuilder = new EnglishFrenchHtmlTranslatorBuilder();
        
        director = new Director(frenchTranslatorBuilder);
        director.Construct();
        
        translator = frenchTranslatorBuilder.GetResult();
        translator.Translate("<p>Hello</p>");
    }
}

// outputs:
//
// 你好
// <p>bonjour</p>

Notes

In some examples of Builder, the product contains a list of its parts. In our case, the properties of the product are our parts. To pass different builders to the same director, we can pass the builder via the construct method rather than the constructor.

The updated Director and Demonstration would look like this:

public class Director
{
    public void Construct(IBuilder builder)
    {
        builder.BuildParser();
        builder.BuildInterpreter();
        builder.BuildRenderer();
    }
}

public static class BuilderDemo
{
    public static void Execute()
    {
        var chineseTranslatorBuilder = new EnglishChineseTextTranslatorBuilder();
        var frenchTranslatorBuilder = new EnglishFrenchHtmlTranslatorBuilder();
        
        var director = new Director();
        director.Construct(chineseTranslatorBuilder);
        
        var translator = chineseTranslatorBuilder.GetResult();
        translator.Translate("Hello");
        
        director.Construct(frenchTranslatorBuilder);
        
        translator = frenchTranslatorBuilder.GetResult();
        translator.Translate("<p>Hello</p>");
    }
}

Summary

Design patterns and their implementation details should be treated as guidelines and tools, and they should ultimately serve the specific needs of your application. There's no universally correct answer—it depends on your particular case and application requirements.

For more information, please see here.