Builder pattern
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
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