📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

The Builder Pattern: More Common in Python Than You Think

ArjanCodes14:19

Transcription

Sometimes creating an object gets messy. If you have a config file, a report, a database query, or even a simple HTML page, all need a ton of options, flags, and nested parts. And before you know it, your constructor turns into a monster, or you're dealing with layers of deeply nested dictionaries. That's where the builder pattern comes in. It lets you build complex objects step by step in a clean and flexible way.

To my own surprise, I never covered this pattern on the channel. So, today we're going to fix that. Now, before I dive in, if you want to learn how to design software from scratch and when to apply certain design patterns, I have a free guide for you. Go to ion.code/design guide. This describes the seven-step process I use when building production systems. The link is also in the description.

As an example, today I'm going to use the builder pattern to build an HTML page. So this is going to be the product that the builder produces. Now before we can even do that, I want to start with a really simple data class that represents an HTML page. So let's say just get started HTML page has a title and a body. And then what we can do is add a render method that then creates the actual HTML content. Now, of course, this is still a very simple thing, right? But let's import this HTML page and then in our main function, let's now create a page like so. So, we have a title and we have a body and then we're going to print the rendered page like so. When we run this, then this is what we get as a result.

Now we can make this a bit nicer, for example, by making the data class frozen so that this is a simple immutable object. Now you could say, "Well, this is totally fine. We don't need any particular pattern to do this," and in a sense, you're right. I mean, in this case, the HTML page is of course very simple. Body is just a string, right? Uh, so, uh, we just put it in here. So it's very basic, but of course, that's not really how HTML is set up. HTML is really can be a really complex structure. So if you wanted to properly represent an HTML page and retain the full structure, you would get a pretty complicated set of variables here, and it would be really hard to actually initialize that properly or set values to it because that's like a deeply nested thing. But even if you wouldn't do that already, HTML of course has lots of other things as well, apart from the title and the body, such as metadata and maybe other things.

Now, the builder pattern is a creational design pattern that separates object construction from the representation. Here's a page from a website called refactoring guru that shows the builder pattern in more detail. This, by the way, is a really helpful resource if you want to learn more about design patterns in general. As you can see, as part of creational patterns, we have the builder pattern here. And this image showcases the reason a builder pattern exists. And when you scroll down, you see various examples of where the builder pattern could make sense. So in this case, there's a house builder, and we have build operations for all of the different parts, instead of having one huge initializer that builds everything all at once. So basically, you configure your object piece by piece. And once you've done that, once you're ready, you build the final object that's often immutable.

And you may think, "Well, that's kind of a very specific use case. You wouldn't use that all that much." But actually, you've probably already seen the pattern used by other libraries. For example, Pandas uses this pattern as well. There's a style option on data frames where you can set various style configuration things like, uh, the caption and the format and a background gradient and a bunch of other things as well, and then finally you convert it to HTML. This is a clear example of the builder pattern. We define step by step what we want to build and what the conditions and the configuration is. And then finally, we build the thing. That's what's happening in this last step. Another example is Matplotlib, where we're also constructing a chart in different steps. So we first start with a bar chart, then we set a y-label, then we set a title, a legend, etc. And finally, we build the plot and show it on the screen. And this, by the way, is a very basic Matplotlib example. But this is also an example of the builder pattern. We build, we set up the thing step by step. And finally, we build and show the thing that we want to have. So that's what the builder pattern does. And you can also imagine that if you have all this information, like, uh, the different fruits, the counts, the the labels, the colors, the y-label, the title, the legend, that's like a whole bunch of stuff. And you don't all want to pass that to an initializer. It's going to be a big mess. So that's why you opt for a builder pattern like this.

Now, back to my HTML page. Let's create a builder that allows us to create an HTML page object step by step. So I'm going to create another class here called HTML builder. And this is going to have a title and the content of the body. And then what you can do is add various methods like, in this case, AI already generated a bunch of things for me, like, uh, it allows me to add a heading or a paragraph. But for example, we could also add a title. And then let's simplify this a bit by just having an empty initializer like so. So now we've created a builder class that allows to add a title. It adds headings, uh, by automatically formatting it. It can add a paragraph to the body content. And then we're going to have a build method that actually creates the HTML page object like so.

So if we now go back into our main function. So instead of having to do it like this, what we can do now is we import the HTML builder and then we're going to create the builder like so. Then we're going to add the elements that we want. So we have the title, which is "My Web Page." We can add a heading like so. I need to learn to type properly. And we add the paragraph. And then we run page is builder.build. And that is going to give us an HTML page. So I think there is still an issue here. So build it should actually not directly call render, but this should actually return an HTML page like so, and then we can print the page. So let's run this and see what we got. So as you can see, we get more or less the same result as we had before, slightly differently formatted, but this is basically what it looks like.

Now, there's one thing we can do to make this a bit easier to use, which is to use self-typing. So at the moment, the HTML builder class, when you call these methods, they all return none. But actually, it's helpful if it returns the object so we can chain, uh, operations on it. So in this case, we're going to let it return self, and we're going to do the same thing here, like so. And what we can do now is, instead of doing builder.builder.build, we can actually do it in as a chain of operations. So we can do .add_title().add_heading().add_paragraph(), and this makes it even simpler to use. So that's the core of the builder pattern.

Again, you might wonder, "Why not, uh, use just a big constructor in this case?" Well, in this case, the HTML page is still relatively simple. It has a title and a body. But what if it has, uh, metadata, buttons, a footer, uh, theme, CSS classes? Uh, if you have to pass everything into one single constructor, it becomes really hard to maintain and error-prone. And the builder pattern here gives you a lot of flexibility, as you can add or remove all these optional parts over time, and also keeps construction logic out of your data model, which improves readability because currently, HTML page remains relatively small. We don't have to worry about how it's being built. We simply worried about how to use it. So that's also a really nice thing. And as we add more complexity, HTML page can remain simple. If we add more features to how you can build an HTML page, the that doesn't affect this class all that much. It mainly affects the HTML builder class.

Now, one thing we could do is add metadata, which is a dictionary of strings to strings, like so. And now we can also extend the render method to take care of that. And this is how you could implement it. So we're adding a meta item for each item in our metadata dictionary. And now in the HTML builder, we could add metadata. And then of course, we would also need that here. Or another thing we could do is add a button, like so. So this is a simple method that gets a label and an on-click link, and then it's going to add this to the body content. So once we have this, we can create a much more complete page. So in this case, we have a builder, we add a title, we add a heading, we add a paragraph, and then, uh, we add a button with a link. And now let's create an HTML page and then render that page data into that file. Let me run this. And then, as you can see, we now have our page.html with the button and the paragraph and the header and everything that we added. As you can imagine, without the builder pattern, this would result in a really big constructor that's very hard to read, or a very big HTML page class that's responsible for both construction and rendering, which is not great from a single responsibility principle point of view.

Now, as a bonus, how can we view this HTML page easily? Before I show you that, if you're enjoying these kinds of deeper dives into real design patterns like the builder pattern, hit the like button and subscribe to the channel. It's a small thing, but it really helps me know which design patterns to cover next and gives the algorithm a little nudge.

Now, in order to view this HTML page, ideally, I'd like to open a browser and then host that HTML page locally. And that's actually what this little class does here. This is an HTML viewer class. It gets a file name that it should display, which is the HTML page that we created. It gets a port and it gets a server, and then it has some helper methods. So there is surf, for example, which creates a TCP server and then opens the browser and then navigates to a particular URL, and then it's serving the file at that particular URL. And finally, there's a start method that sets the directory to the path of the file and then starts the HTTP server from that spot. So what you can do now is actually run the script, and then it's going to open the browser and show the HTML page that was created by the builder pattern. Back to our IDE, I can simply cancel and stop the server by a keyboard interrupt. This is a really clean, simple way to show the page that we created locally.

So before I end the video, when should you actually use the builder pattern? Now, of course, I talked about the use case of when you're building complex objects with lots of optional parts. That's a great place where the builder pattern can help. Things like configuration files, nested queries, UI components, stuff like that. It's great when you want a clean, fluent API or you need to really separate construction from logic. And the nice thing about the builder pattern is that because you built the object step by step, it's very readable. It's very flexible because it's really easy to add or skip parts. It's also quite safe because it avoids giant constructors or fragile nested dictionaries.

But the builder pattern also introduces some boilerplate. Uh, it's more boilerplate than just having a simple data class. And if you have an object that has just a few fields, then of course, it's going to be overkill. Another problem, potential problem with the builder pattern is that it's easier to make mistakes. For example, in the main file, if you forget to add the build operation, then it's, uh, not going to work. Uh, also, if you have many, many, many different elements that you need to set, then you may forget one, and you have to sift through all of these method calls to figure out which one you forgot. So if something is really complex, then you might want to shift to, uh, storing the thing in a separate JSON or configuration file and then read from a file instead of calling all these separate methods.

But overall, though, if your object has five-plus optional fields and you expect it to grow over time, then the builder pattern is actually a good choice. Otherwise, I'd say don't use it, but keep it simple. But I'd like to hear from you. Have you used the builder pattern before? What other design patterns do you regularly use? Are there any patterns that you'd like me to cover on this channel in more depth? Let me know in the comments.

Now, next to patterns, there are also anti-patterns, things you definitely shouldn't do. Check out this video to learn more. Thanks for watching and see you next.