When we start developing applications we come across various types of problems. We solve these problems by ourselves or by consulting others. But when our applications grow in size and complexity, it might become difficult to tackle the problems that arise. Moreover, these problems became so common that developers came up with a general solution on how to tackle these problems.
Formally, a design pattern provides a general reusable solution for the common problems that occur in software design. Although the concept of design patterns existed for quite some time, it was never formalized until the famous Gang of Four(GoF) came together. This Gang of Four consisting of Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides wrote the book Design Patterns: Elements Of Reusable Object-Oriented Software.
There are around 26 design patterns that can be classified into 3 main types.
Creational — these types of design patterns are concerned with the creation of objects such that it is suitable for the situation we are working with.
Structural — these types of design patterns are more involved with the composition of objects such that relationships between objects are kept simple.
Behavioral — these types of patterns focus on managing the communication between contrasting objects in a system.
This article will discuss a few important examples for each type of design pattern.
The singleton design pattern focuses on restricting the instantiation of a class to only one object. This pattern simply instantiates a class only if it does not exist. If the class has been previously instantiated, the previously instantiated object is returned.
This is very useful when you want to make sure an object is shared across your application by providing a single point of interaction for functions.
Some applications of the singleton pattern include logging, caching, database connections, etc.
Let's have a look at one such example involving a simple cache.You can see that the book saved in First can be retrieved from Second. This is because we only allowed one instantiation of the cache object to be shared amongst these two classes.
Although Singleton has its valid use cases, if you find the need to use Singelton repeatedly in your application it indicates that we may need to re-evaluate our design. Furthermore, since Singleton is tightly coupled, it creates problems during testing.
This design pattern also comes under the creational type of design pattern. This pattern also focuses on the creation of objects. But rather than using a constructor for the creation of several types of objects, the factory pattern uses a generic interface for creating objects, where we can specify the type of factory object we wish to be created.
This results in a much simpler interface where we are only required to input what type of object we want to create and its properties. The factory method would create the appropriate object with the given properties and return it. This is specifically useful when the object creation process is complex and needs to be decoupled.
A typical example use for a factory pattern would be a UI factory where we create UI components based on the inputs to the UI factory.
Let’s have a look at a sample code to understand the factory pattern more.As we can see from the above example, the dish factory creates the appropriate dish type based on the inputs given.
You can easily introduce unwanted complexity to your application by applying this design pattern to the wrong type of problem. You have to make sure your design goal will be achieved by the pattern being implemented.
The Facade pattern focuses on providing a high-level interface for a body of code that is complex in nature. It can be thought of as an API for your code body, hiding the underlying complexities and only presenting a simplified interface. Because of this, the Facade pattern falls under the structural pattern.
This pattern can be coupled with other patterns as well. For example, Facade objects are often Singletons because only one Facade object is required.
When using the Facade pattern, you must note the performance costs involved and make sure it is worth the level of abstraction.
Let’s have a look at a simple example involving the facade pattern.As seen in the above code example, the example involving the facade defines a higher-level interface that removes the unnecessary duplicate code for each HTTP call for posts and abstracts them.
Decorators are a structural design pattern that mainly aims towards code reusability. A decorator is simply a way of wrapping a function with another function to extend its existing capabilities. You “decorate” your existing code by wrapping it with another piece of code. This concept will not be new to those who are familiar with functional composition or higher-order functions.
Decorators allow you to write cleaner code and achieve composition. It also helps you extend the same functionality to several functions and classes. Thereby enabling you to write code that is easier to debug and maintain.
Decorators also allow your code to be less distracting as it removes all the feature-enhancing code away from the core function. It also enables you to add features without making your code complex.The above code example uses a decorator to extend the functionality of the multiply function. allArgsValid is a decorator function that receives a function as an argument. This decorator function returns another function that wraps the function argument. Moreover, it only calls the argument function, when the arguments being passed onto the argument function are valid integers. Otherwise, an error is thrown. It also checks for the number of parameters being passed on and makes sure that it does not exceed or deceed the required number of parameters.
The decorator pattern helps us to wrap functions with new behavior without having to worry about modifying the original function. They also enable us to stop relying on many subclasses to get the same benefits.
On the other hand, the overuse of decorators might create many small similar objects into our namespace. It might also be difficult for developers unfamiliar with the pattern to handle this in the beginning.
The Observer is a design pattern in which an object(known as a subject) maintains a list of objects(known as observers) who are interested in the subject. The subject notifies the observers when there is a change in the state of the subject. When the subject needs to notify an observer because of this state change, it can send out a notification which may include relevant data as well. The observers can also stop themselves from being notified if required.
Angular developers would be familiar with observables and subjects. In fact, the popular RXJS library heavily uses subjects.
Let’s have a look at a simple example of the observer pattern.There are loads of other design patterns that you should be aware of, apart from the 5 covered here. Moreover, the design patterns teach you the approach to be taken to solve your type of problem, not a copy-paste solution for you to use across your applications. It is up to the developer on how to apply the design pattern to solve the problem at hand.
You must also note that the wrong design pattern applied to the wrong problem, can cause poor performance results.
You can refer to the examples in my Bit collection over here, install or ‘import’ (clone) into your own Bit workspace.