Builder pattern

By Martin McBride, 2021-10-07
Tags: creational pattern builder
Categories: design patterns


Builder pattern is a creational design pattern. It can be used when constructing complex objects, where it has several advantages:

  • It can divide the creation process into multiple steps, which can make the code clearer when constructing a complex object.
  • It allows different representations of an object to be created, using the same construction code, by supplying a different builder.
  • It is useful for creating complex immutable objects. It allows the builder can be mutable during the construction process, but the final object can still be immutable.

The builder pattern decouples the process of building an object from the representation of the object.

Example sales report generator

As a simple example, consider a simple report generator that:

  • Accepts a list of daily sales figures.
  • Produces a report including the average, minimum and maximum daily sales.

We would like the report to be available in plain text format or simple HTML, and possibly some other formats in a future version of the software.

Basis of the builder pattern

The basic stages of creating the report are the same, no matter what format is used. We must:

  • Accept the input data.
  • Add report header (for example the HTML header).
  • Calculate the average, min and max, and add them to the report.
  • Add the report footer.

In the builder pattern, we split the responsibility for creating a report between two classes:

  • A ReportGenerator that performs the calculations, and controls the report creation.
  • An IBuilder that creates the text or the report.

In fact, IBuilder is an interface. We can have several actual builders, for example, a TextReportBuilder to create a text report, and an HtmlReportBuilder to create an HTML report. The ReportGenerator does the same in all cases, so by passing in the correct builder we can obtain the report format we want.

Here is how the classes relate to each other:

The builder interface

The builder interface, IBuilder, contains all the methods required to build the report:

class IBuilder:

    def addHeader(self):
        pass

    def addMean(self, value):
        pass

    def addMin(self, value):
        pass

    def addMax(self, value):
        pass

    def addFooter(self):
        pass

    def build(self):
        return ''

This interface allows us to add all the elements to the report, and then obtain the result by calling the build method, which returns a string. Since this is an interface, of course, it doesn't create a report, we will need to use one of the concrete classes below to do that.

ReportGenerator

Here is the report generator class:

class ReportGenerator:
    def __init__(self, data):
        self.data = data

    def create(self, builder):
        builder.addHeader();
        builder.addMean(sum(self.data)//len(self.data));
        builder.addMin(min(self.data));
        builder.addMax(max(self.data));
        builder.addFooter();

        return builder.build()

This class is very simple. The constructor accepts the sales data, data, which is simply a list of numbers representing the sales each day.

The create method accepts a builder object (which must have an IBuilder interface) and uses it to create the report.

The task of creating the report is neatly divided:

  • The ReportGenerator controls the overall structure of the report and performs the necessary calculations.
  • The builder does the detailed formatting on the report.

To create a report we will need to implement at least one builder class.

TextReportBuilder

Here is a builder to create a report formatted as plain text:

class TextReportBuilder(IBuilder):

    def __init__(self):
        self.report = ''

    def addHeader(self):
        pass

    def addMean(self, value):
        self.report += 'Mean {}\n'.format(value)

    def addMin(self, value):
        self.report += 'Min {}\n'.format(value)

    def addMax(self, value):
        self.report += 'Max {}\n'.format(value)

    def addFooter(self):
        pass

    def build(self):
        return self.report

The builder constructs a string object containing the report.

The addMean method adds a line to the report containing the mean value. The addMin and addMax methods work in a similar way.

The addHeader and addFooter methods do nothing. because the simple text report has no header or footer.

Here is how we use these classes to create a text report:

data = [2, 5, 3, 8, 7]
report = ReportGenerator(data).create(TextReportBuilder())
print(report)

We create a ReportGenerator using the data, then call create using a new TextReportBuilder, and print the result. Here is the report:

Mean 5
Min 2
Max 8

HTMLReportBuilder

If we also wish to create an HTML formatted report, we simply create a new builder, again implementing IBuilder:

class HTMLReportBuilder(IBuilder):

    def __init__(self):
        self.report = ''

    def addHeader(self):
        self.report += '<html>\n'

    def addMean(self, value):
        self.report += '  <p>Mean {}</p>\n'.format(value)

    def addMin(self, value):
        self.report += '  <p>Min {}</p>\n'.format(value)

    def addMax(self, value):
        self.report += '  <p>Max {}</p>\n'.format(value)

    def addFooter(self):
        self.report += '</html>\n'

    def build(self):
        return self.report

This is very similar to the previous class, but addMean etc wrap the line in HTML '

' tags. The class also adds a simple HTML header and footer to the report. Here is the code to call it:

data = [2, 5, 3, 8, 7]
report = ReportGenerator(data).create(HTMLReportBuilder())
print(report)

This is the same as before, we just use a different builder. Here is the result:

<html>
  <p>Mean 5</p>
  <p>Min 2</p>
  <p>Max 8</p>
</html>

Summary

This has been a very simple example of a builder class. In the case where the report might be much more complex, and we might want multiple outputs (such as full HTML, XML, or PDF), then splitting the code in this way can be very useful.

Builder can be used wherever you need to construct a complex object, and particularly where you might want different representations of the same object.

See also

If you found this article useful, you might be interested in the book NumPy Recipes or other books by the same author.

Join the PythonInformer Newsletter

Sign up using this form to receive an email when new content is added:

Popular tags

2d arrays abstract data type alignment and angle animation arc array arrays bar chart bar style behavioural pattern bezier curve built-in function callable object chain circle classes clipping close closure cmyk colour combinations comparison operator comprehension context context manager conversion count creational pattern data science data types decorator design pattern device space dictionary drawing duck typing efficiency ellipse else encryption enumerate fill filter font font style for loop formula function function composition function plot functools game development generativepy tutorial generator geometry gif global variable gradient greyscale higher order function hsl html image image processing imagesurface immutable object in operator index inner function input installing iter iterable iterator itertools join l system lambda function latex len lerp line line plot line style linear gradient linspace list list comprehension logical operator lru_cache magic method mandelbrot mandelbrot set map marker style matplotlib monad mutability named parameter numeric python numpy object open operator optimisation optional parameter or pandas partial application path pattern permutations pie chart pil pillow polygon pong positional parameter print product programming paradigms programming techniques pure function python standard library radial gradient range recipes rectangle recursion reduce regular polygon repeat rgb rotation roundrect scaling scatter plot scipy sector segment sequence setup shape singleton slice slicing sound spirograph sprite square str stream string stroke structural pattern subpath symmetric encryption template tex text text metrics tinkerbell fractal transform translation transparency triangle truthy value tuple turtle unpacking user space vectorisation webserver website while loop zip zip_longest