Too Many __init__ Arguments Walk Into a Bar… Meet the Builder Pattern

I read a python class definition recently with so many __init__ arguments that a fist fight broke out. Let's just say... it wasn't pretty. "If only they'd known about the builder method, then they'd be able to get along," I thought.

The builder method

The builder pattern is a creational design pattern that seeks to make creating objects flexible by separating the object from the construction of the object. In Python, you can see this pattern in action in a variety of popular libraries:

  • SQLAlchemy queries (select().where().order_by())
  • Plotly charts (step-by-step figure construction)
  • PySpark (SparkSession.builder.appName().getOrCreate())

You can see in PySpark, for example, that when you load data into a data frame, there are a lot of options. The library does a good job of setting reasonable defaults for all of the options, but by utilizing the builder method, you can set each of those options in a readable and flexible way for the use case at hand.

Example of PySpark using builder method

The builder pattern involves three main parts:

  1. Product — The complex object you're trying to create
  2. Builder — The builder is the object responsible for constructing the product
  3. Director (optional) — An object that helps to define the ordering of construction steps

This pattern is particularly useful in the following cases:

  • You need to construct a complex object with many optional parameters
  • You need to separate the construction logic from the object representation
  • You need to create an immutable object with some logic in the construction of that object

Let's look at an example.

An example

Imagine that you've got an object that defines a loan application for a banking application you're creating. Those things are very complex! There's a ton of information that we will need to fill into the loan application to make sure everything is accounted for. For the sake of the example, we'll simplify slightly to not include everything you might need for a loan application, but hopefully enough to get the point across.

In python, if we were to create a loan application class, it might look something like this.

Example loan application class

Yep, that's a lot of optional arguments.

In this case, we may also want the application to be immutable, and separating the construction of the object from the object itself will certainly simplify things and give us some additional flexibility.

So, how do we implement the builder method? Well, we know there are some required arguments for the loan application class, so we can build those into the new builder class with some added validation on those required parameters.

Loan application builder 1

Next, we can define each of the optional parameters with a setter function to update the LoanApplication object we are storing in the builder.

Loan application builder methods

Notice here that we are returning self, which is the instance of the LoanApplicationBuilder. This will allow us to chain these different function calls to modify the loan application.

To finish the builder class, we need some way for the builder to return the product, and we'll do that with an additional build method.

Loan application builder build method

Finally, to make this whole thing more pythonic, we can modify the product (LoanApplication) to use a dataclass in order to clean up a lot of the boilerplate code we wrote before with the optional arguments. You can see that in GitHub. 😁

Now that we've got the builder method written, how do we use it?

First, we instantiate the builder with the required arguments. Then, we can chain any of the optional methods to the builder itself to update the application. Finally, we can call the build() method to return the LoanApplication object.

Example usage of builder method

What's great is this is extremely readable. It's also incredible flexible, allowing us to create different versions of loans (e.g., mortgage, car loan, etc.), which have different optional parameters we could want to specify, without needing different products.

Now that we've implemented the builder method, there aren't any more fights, at least until the next design session starts up. 😉

Happy coding!

Previous
Previous

The Context Object Pattern: Stop Passing 11 Arguments Everywhere

Next
Next

Factories are friends