Don’t try to reinvent the wheel. Just learn from those who have already done it well.
Design patterns are general, reusable solutions to common software engineering bugs. They are good practices developed by trial and error, labor, and countless hours of debugging by programmers in the past, so that developing can be easier, faster, cleaner, bug-free for us today. In short software design patterns are all about not reinventing the wheel.
Design patterns gained popularity after the publishing of the book Design Patterns: Elements of Reusable Object-Oriented Software was published in 1994 by the so-called “Gang of Four” — “GoF”.
It is important to note that a lot has changed since then. Although design patterns are still relevant and important to know, in most languages they are implemented/replaced by new features.
In this article, we are going to study two of the most famous OOP Design Patterns, with examples.
In object-oriented programming, the builder is a design pattern intended to offer a scalable approach to different object creation issues. The purpose of the Builder pattern is to distinguish the creation of a complex object from its representation.
Let's understand this with an example. We first define a Person class with several attributes such as the name, the surname, etc. We then create an object (an instance) of that class, Joe.
It is common — and perfectly normal for several attributes to be left undefined (None). For example, Mr.Joe didn’t want to provide information about his age, height, ethnicity, gender.
In order to create an object, each and every attribute has to be passed as an argument to the constructor! Therefore, even though the user didn’t provide that information, we have to pass them as undefined, null, None (depending on the language).
This requirement creates a big overhead. Imagine having to create 10 objects with 30 attributes each! It's easy to miss a value, to wrongly align an argument etc. Furthermore, the code becomes an unreadable raw spaghetti.
The solution to this problem is given by the Builder Pattern. What if we could write another class — a PersonBuilder, solely responsible for the creation of the object? This class would contain:
- A constructor with only the absolutely required attributes (let's just say the name, in our instance)
- A build() method that returns the actual Person object
- Set() functions to later define the other attributes (e.g ID, gender, surname)
At last, the actual Person class would now require only one argument. Let's get our hands dirty:
Now the actual creation of an object is as simple as:
Null Object Pattern
Let's assume we want to check if a user is logged in, and if he is, display a welcome message with his name.
Now, image a more complex scenario, a pandemonium of if/else statements checking for null values may take over the code. Exceptions and cases can be easily missed, and the code eventually becomes unreadable.
How can we reduce the statements and handle all the exceptions in one place? Here is where the null object pattern is useful. Instead of checking for every event if an attribute is set, we can just create a new class, a null User.
A single initial if statement can wrap and replace dozens or even hundreds of other similar checks:
In fact, Python3 (and in many other OOP languages), offer other, even simpler way to construct a class, using named parameters. Named parameters can replace both Null Object and Build design patterns:
Then, if modern languages already provide alternative solutions, why should one study those and the other types of Design Patterns? This is a legitimate question.
Their value lies in a deeper understanding of the engineering processes. Although most of them have already been internally implemented by languages — by using other technologies or ideas, they still are relevant and useful to know. The knowledge of them enhances the developer’s ability to identify the common problems they solve and thus find a proper and efficient solution. Design patterns are all about simplicity, clarity, and bug reduction.