First of all, let's start with an explanation of what design patterns are. In the simplest terms, they allow us to re-use code for re-occurring problems. Instead of solving the same problems again and again, we can re-use efficient code that already works. Why would you use design patterns? The reasons are that they are:
- Well documented and tested
- Reusable in many different situations
- Saving you time
It's important to note that these patterns are universal, which means they apply to other programming languages/frameworks/libraries. However, in this article, we use Node.js to implement and understand them.
There are three types of design patterns:
- Creational - the creation of the object instances
- Structural - the way the objects are designed
- Behavioural - how objects interact with each other
Creational Design Patterns
In this article, you learn about the Creational Design Patterns. They are concerned with the creation of object instances. In the following articles, you will learn about Structural and Behavioural Patterns too.
The Creational Design Patterns covered in the article are:
Without further ado, let's start with the Singleton design pattern.
We can get a glimpse of the purpose of this design pattern from its name - SINGLEton. We use this design pattern when we only want a single instance of a class. That means we cannot create multiple instances - just one. If there is no instance, a new one is created. If there is an existing instance, it will use that one.
Let's see an example with the Singleton pattern implemented:
As you can see, there is a lot of boilerplate to implement the Singleton. Thus, let's see a shorter way of implementing this design pattern. The solution is to remove the class Singleton altogether and export a new instance of the class DatabaseConnection.
Why does this work? In Node.js, it works because of the module caching system. The module caching system says that "modules are cached after the first time they are loaded" (source - Node.js docs). That means in the second example above, the new instance exported is cached and re-used each time it's required.
Therefore, these are the two ways of implementing the Singleton pattern in NodeJS. To recap:
- Singleton is useful when you want only one instance of a class.
- In NodeJS, we can make use of the module caching system to export an instance directly.
We can get an idea of what this design pattern does by looking at its name. The Factory Design Pattern allows us to define an interface or abstract class used to create an object. We then use the interface/abstract class to instantiate different objects.
Consider an application where we have to create and use all types of vehicles. There are motor vehicles (cars, buses, trucks, motorcycles), railed vehicles (trains, trams), aircraft (airplanes, helicopters), and watercraft (ships, boats). Thus, instead of creating instances by calling the constructor of each class individually, we can implement the Factory pattern as follows:
Instead of creating an instance of each class individually, you can use the VehicleFactory and specify the type. You would create a new Car instance as follows:
The benefit of using the Factory design pattern is that it decouples the objects' constructions from the objects themselves. You can also introduce new objects into your application without breaking existing code. Lastly, it helps you organize your code better because all the code related to creating instances is in one place. To recap:
- Factory pattern provides an interface/abstract class for creating objects.
- You can create different objects by using the same interface/abstract class.
- It improves the structure of the code and makes it easier to maintain it.
The Builder design pattern allows us to separate the construction of objects from their representation. Thus, it simplifies the code that creates complex objects. It might be overkill with simple objects, but with complex objects, it simplifies creating them.
To help you with understanding this design pattern, let's think of an example. Let's say we want to build cars. In this example, we would have the Car and CarBuilder classes.
Now we create cars using the CarBuilder class instead of the Car class. We do it as follows:
Using the Builder design pattern makes the creation of complex objects less prone to errors because you can easily understand what each parameter does. For contrast, here is how we would create cars without the CarBuilder class:
You can see it can become confusing. What do those two boolean values ("true") mean? Now imagine the object is more complex; creating the object would be confusing, and the chances of introducing errors would be higher.
Thus, the Builder design pattern is useful to separate the creation and representation of complex objects.
Thus, we create new objects by cloning the prototype's values, which can also be called a sample object. That means the prototype acts as a blueprint for the new objects. One significant benefit of using this design pattern is that functions defined in objects are created by reference. That means all objects point to the same function instead of having their copies of that function. In simpler terms, the prototype's functions are available to all the objects inherited from the prototype.
Ok, enough talk. Let's see an example so you can understand better what this design pattern is about. We continue with the car scenario.
To create a new object from a prototype, we can use the Object.create() method. The second object - secondATV - has the same values as the first object - atv. To make sure it works correctly, try calling the "mud" function or printing any object property.
Another way of using the Prototype Design pattern is by specifying the prototype in the "class". You can see an example below:
On the same note, both instances have access to whatever is defined in the atvPrototype object. Moreover, they point to the same function, instead of having their "mud" function.
In conclusion, the Prototype design pattern is useful, especially when you want your objects to share the same functions or properties. It also acts as a blueprint for all your new objects.
- It uses prototypal inheritance.
- Each object inherits from other objects.
- New objects are created by following a blueprint, called Prototype.
- A function defined on the prototype is inherited by all new classes.
- The new classes point to the same function, rather than having individual copies.
The article is part of my #learninpublic process. Thus, it's important to note that the article is a starter, and you can dig deeper. I recommend the book Design Patterns: Elements of Reusable Object-Oriented Software if you want to go deeper.
In this article, you learned how to create objects in various ways. You can create new objects using the following design patterns:
Catalin publishes programming and software development articles on his blog.