How to write clear, bug-free code using software Design Patterns

Don’t try to reinvent the wheel. Just learn from those who have already done it well.

George Foreman

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.

Builder Pattern

Definition

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.

Example

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.

Normal way of creating an object (Python)

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.

Counterexample

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:

Create a builder class (Python)

Now the actual creation of an object is as simple as:

Create an object using a class builder

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.

Null Object Class (Python)

A single initial if statement can wrap and replace dozens or even hundreds of other similar checks:

Null Object creation example (Python)

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:

Named Parameters — Python

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.

DevSecOps & Cloud Engineer