Design patterns are reusable solutions to problems that we often face as software engineers. It is not a component or class that can be reusable. It is more like a statement of the problem and solution that works best for that specific problem.
The design patterns can be used as a plug-n-play part of the software system. However, the design patterns were never meant to hinder the natural thinking process of problem-solving in software engineering. Design patterns are not one-size-fits-all solutions. There can’t be any substitute for thinking for a solution from a different perspective.
However, these design patterns can be of incredible help in real life if used for the right reasons. When used strategically, they can save lots of time and make programmers more productive by not letting them go after the unnecessary wheel reinventing process. Instead, software engineers can benefit from already existing solutions.
There are almost 26 design patterns that can be further classified into three types. Several caveats concern the software engineers that how and why each pattern is used.
To keep things simple, I will discuss six design patterns that every developer should know about.
1. Strategy Pattern
Strategy patterns define a set of algorithms that encapsulate each one and are interchangeable. In other words, if you have a family of algorithms consisting of Algorithm A, B, and C for a particular software system, you can use these as a plug-n-play and independent solutions.
If you need to change something in a specific algorithm, you don’t have to change the program using it.
For example, if you have a sorting algorithm that sorts a list of numbers for the lowest to the highest number, the strategy pattern allows you to inject a different sorting algorithm independently from the list. The implementation of the list remains stable while you inject different sorting algorithms.
2. Singleton Pattern
In simple words, in the case of a singleton pattern, you always get the same instance whenever you ask for an instance.
So, how do you make sure that only one object is ever initiated from a class?
The answer is – Make the constructor of the class “private”. This ensures that only the members of the class have access to the constructor declared as private.
Providing global access to an instance can raise concerns for some software engineers. Defining variables, functions, or even objects in a global namespace isn’t recommended. It might be a security risk, or things can get complicated to understand without context.
3. Factory Pattern
Let’s say you have a bunch of classes; class A, B, and C, all inherited from a superclass or implemented some interface “I”. Class A, B, and C can be treated as the same type.
Now, at a particular point in your software, you need to instantiate one of these classes. But at that specific point, you don’t know which one you want to create. If you know which class you need to instantiate, this isn’t necessarily a factory design method.
In simple words, it allows you to create a wrapper around the keyword new. Instead of creating a new Cat from the Animal class using the new keyword, you create Cat by calling a factory method.
The factory pattern takes out the responsibility of creating an object from the factory class. When you try to instantiate an object, a factory pattern encapsulates that instantiation so that you can use the factory whenever you want to instantiate, and the factory takes care of instantiating.
4. Observer Pattern
You can create a system where observers keep asking the app about its internal state over a specified interval. But, instead of sending change requests to the app now and then, the optimal solution is to be notified from the other app whenever the app changes the internal state(weather temperature). Whenever the internal state of the subject changes, the observers are notified.
Another example is how you get notified when someone you follow on Twitter writes a new tweet. In this case, you are the observer, and the person you follow is the subject, just like the weather app in the above example.
There can be more than one follower or an observer of a subject. But, the app or subject needs to know about each observer to send notifications. To get the notifications from the person, you need to follow the person first. In other words, the one who got observed should know about observers as well.
5. Decorator Pattern
Let’s say you have a cake and you want to decorate it. The decoration doesn’t change the cake itself; it’s just an addition to the cake. You are just adding some icing, chocolate chips, or sprinkle, perhaps.
The decorator pattern is used to enhance the functionality of an object at run time without affecting its core behavior. We use abstract classes or interfaces to implement the essence of the decorator pattern.
If you are wondering why I didn’t mention the inheritance or composition, you should understand that these two are used at compile time and not at run time. Changes are implemented to all instances of the inherited class, and that’s where the decorator pattern comes in.
6. Builder Design Pattern
The builder pattern is used to create complex objects using a step-by-step sequence of actions. This is very similar to the abstract factory design pattern where you find a factory for a particular object, and that factory gives you an object in one step. The major difference here is that the builder design pattern focuses on “How,” whereas the abstract factory pattern emphasizes “What”.
This pattern should be used only when creating a complex immutable object using the same sequence of actions or object-building process. One of the most common examples of the Builder design pattern is the creation of pizza. You can’t add pizza topping in one go or random order. Instead, there is always a step-by-step sequence of actions that need to be taken to add pizza toppings.