Builder pattern


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.

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

Prev

Popular tags

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