Press "Enter" to skip to content

Builder Design Pattern in C#

Last updated on September 15, 2023

In this article, I’ll demonstrate how to use the Builder Design Pattern.

 

Video version here

 

What it is, and when to use it

The Builder Design Pattern is a “creational” pattern – you use it to create instances of objects.

You might want to use this pattern when you have a class with a large number of parameters in its constructors. Or, if the class has several different constructors, most of them with many parameters.

Another time you might use it is if the class does not have complex constructors, but has many properties that need to be set, before you can use some of its functions.

With the Builder pattern, you create a new function (or functions) that set some of the parameters, or properties, for you. This makes your calling code shorter, and easier to read and understand. It can also let you set consistent values for certain parameters, if your calling code needs them set a specific way, for certain types of similar objects.

 

Non-Pattern Version

Here is a class that could be used to build a report. It has five parameters in its constructor. However, a real report-generating class could easily have many more parameters.

Report.cs

using System;
namespace Engine.BuilderPattern.NonPatternVersion
{
    public class Report
    {
        public enum SortingMethod
        {
            BySalesperson,
            ByTaxCategory
        }
        private DateTime _fromDate;
        private DateTime _toDate;
        private bool _includeReturnedOrders;
        private bool _includeUnshippedOrders;
        private SortingMethod _sortBy;
        public Report(DateTime from, DateTime to,
                      bool includeReturnedOrders, bool includeUnshippedOrders, SortingMethod sortBy)
        {
            _fromDate = from;
            _toDate = to;
            _includeReturnedOrders = includeReturnedOrders;
            _includeUnshippedOrders = includeUnshippedOrders;
            _sortBy = sortBy;
        }
        public object CreatePDFReport()
        {
            // Pretend this object is a PDF report, 
            // built for the sales that match the passed-in constructor parameters.
            return new object();
        }
    }
}

 

This unit test method shows how you might use the class. You need to pass in all the parameters, every time you instantiate a Report object.

Notice that the last three parameters of the Tax reports are the same values. In this example, when we create a tax report, we always want to use those values for those parameters.

The commission reports also use their own (different) values for the last three parameters. Those are the “standard” values for those three parameters – for commission reports.

TestNonPatternReport.cs

using System;
using Engine.BuilderPattern.NonPatternVersion;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.BuilderPattern.NonPatternVersion
{
    [TestClass]
    public class TestNonPatternReport
    {
        [TestMethod]
        public void Test_BuildReports()
        {
            DateTime now = DateTime.UtcNow;
            Report currentMonthTaxReport =
                new Report(new DateTime(now.Year, now.Month, 1),
                           new DateTime(now.Year, now.Month, 1).AddMonths(1).AddSeconds(-1),
                           false, true, Report.SortingMethod.ByTaxCategory);
            Report currentYearTaxReport =
                new Report(new DateTime(now.Year, 1, 1),
                           new DateTime(now.Year, 12, 31),
                           false, true, Report.SortingMethod.ByTaxCategory);
            Report currentMonthCommissionReport =
                new Report(new DateTime(now.Year, now.Month, 1),
                           new DateTime(now.Year, now.Month, 1).AddMonths(1).AddSeconds(-1),
                           false, false, Report.SortingMethod.BySalesperson);
            Report currentYearCommissionReport =
                new Report(new DateTime(now.Year, 1, 1),
                           new DateTime(now.Year, 12, 31),
                           false, false, Report.SortingMethod.BySalesperson);
        }
    }
}

 

 

Pattern Version

In this example, which uses the Builder Design Pattern, we have the same code for the Report class.

Report.cs

using System;
namespace Engine.BuilderPattern.PatternVersion
{
    public class Report
    {
        public enum SortingMethod
        {
            BySalesperson,
            ByTaxCategory
        }
        private DateTime _fromDate;
        private DateTime _toDate;
        private bool _includeReturnedOrders;
        private bool _includeUnshippedOrders;
        private SortingMethod _sortBy;
        public Report(DateTime from, DateTime to,
                      bool includeReturnedOrders, bool includeUnshippedOrders, SortingMethod sortBy)
        {
            _fromDate = from;
            _toDate = to;
            _includeReturnedOrders = includeReturnedOrders;
            _includeUnshippedOrders = includeUnshippedOrders;
            _sortBy = sortBy;
        }
        public object CreatePDFReport()
        {
            // Pretend this object is a PDF report, 
            // built for the sales that match the passed-in constructor parameters.
            return new object();
        }
    }
}

 

Now, we have a new ReportBuilder class. This class has methods that let us reduce the number of parameters needed to construct a Report object. It also sets the correct “standard” parameter values for the different types of reports – the Tax reports and the Commission reports.

ReportBuilder.cs

using System;
namespace Engine.BuilderPattern.PatternVersion
{
    public class ReportBuilder
    {
        private static Report CreateTaxReport(DateTime from, DateTime to)
        {
            return new Report(from, to, false, true, Report.SortingMethod.ByTaxCategory);
        }
        public static Report CreateMonthTaxReport(int month, int year)
        {
            return CreateTaxReport(new DateTime(year, month, 1),
                                   new DateTime(year, month, 1).AddMonths(1).AddSeconds(-1));
        }
        public static Report CreateYearTaxReport(int year)
        {
            return CreateTaxReport(new DateTime(year, 1, 1),
                                   new DateTime(year, 12, 31));
        }
        private static Report CreateCommissionReport(DateTime from, DateTime to)
        {
            return new Report(from, to, false, false, Report.SortingMethod.BySalesperson);
        }
        public static Report CreateMonthCommissionReport(int month, int year)
        {
            return CreateCommissionReport(new DateTime(year, month, 1),
                                          new DateTime(year, month, 1).AddMonths(1).AddSeconds(-1));
        }
        public static Report CreateYearCommissionReport(int year)
        {
            return CreateCommissionReport(new DateTime(year, 1, 1),
                                          new DateTime(year, 12, 31));
        }
    }
}

 

Now, to instantiate a Report object, we can call the methods in the ReportBuilder class, as shown in this unit test code.

TestPatternReport.cs

using Engine.BuilderPattern.PatternVersion;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.BuilderPattern.PatternVersion
{
    [TestClass]
    public class TestPatternReport
    {
        [TestMethod]
        public void Test_BuildReports()
        {
            Report currentMonthTaxReport =
                ReportBuilder.CreateMonthTaxReport(4, 2017);
            Report currentYearTaxReport =
                ReportBuilder.CreateYearTaxReport(2017);
            Report currentMonthCommissionReport =
                ReportBuilder.CreateMonthCommissionReport(4, 2017);
            Report currentYearCommissionReport =
                ReportBuilder.CreateYearCommissionReport(2017);
        }
    }
}

 

 

You don’t need to use a static method in the ReportBuilder class. That is just the way I chose to do it in this example.

You could also use a ReportBuilder class that is instantiated. You could pass values into its constructor, or set values on its public properties. Then, the builder class would use those values to “build” the class it is instantiating.

As long as you are simplifying object instantiation, by also reducing the parameters (or properties you need to set in your calling code), you are using the basic concept of the Builder Design Pattern.

 

Related patterns

Composition over Inheritance

Factory pattern

Wrapper/Façade Pattern

 

All my design pattern lessons

Source code for my design pattern lessons

2 Comments

  1. Evan
    Evan May 17, 2017

    Really enjoy your lessons! Keep them coming! Thank you.

Leave a Reply

Your email address will not be published. Required fields are marked *