This article will:
I want to help you avoid the same mistake.
It might seem obvious, but there really should be a good reason for using a particular type of design pattern.
A pattern is a reusable solution that can be applied to commonly occurring problems in software engineering.
Using a design pattern helps reduce the time spent on how the code should look.
Not only that, a good design pattern enforces the DRY (Do not Repeat Yourself) concept that helps to prevent your codebase from growing large and unwieldy.
Design patterns also help team members collaborate, especially in a situation where everyone on the team is familiar with the pattern in question. Every team member will definitely communicate better while handling a uniform style (pattern) in building up a project.
How can you recognize a good design pattern?
As funny as this may sound, a good pattern needs to have a name and a precise, detailed structure. This is not at all the same as mere habits.
Every developer has a way of solving a specific problem (file upload, for example). When a file needs to be handled in any of our projects, we gladly rush to implement this specific solution.
Can we call this a pattern? Definitely not. A good or accepted design pattern must be related with existing patterns. Not only that, it must be approved by other developers.
Examples of how the pattern can be used and a detailed documentation cannot be overemphasized.
Design pattern categories
- Constructor pattern
- Prototype pattern
- Module pattern
- Singleton pattern
- Factory pattern
- Observer pattern
1. Constructor pattern
This is one way to create a constructor pattern:
For you to access the properties of a function in a constructor pattern, it needs to be initialized. This pattern is useful when thinking about Object Oriented Design.
const object = new ConstructorObject();
ConstructorObject should behave like a constructor. One of the drawbacks of this pattern is that it does not support inheritance. A property shared among different objects will always be repeated.
2. Prototype pattern
In constructor pattern, the method or property set in the object is always
redefined when it’s called upon. A better way to resolve this is to create a function inside the prototype function.
With this in place, functions called on instantiation won’t redefine themselves. But a prototype pattern also has a downside. A property is easily shared among all functions even when it’s not needed. You don’t have control over your properties being private or public. It’s automatically public:
3. Module pattern
Module pattern is a bit of an improvement over prototype pattern. In module pattern, you can set different types of modifiers (both private and public). There is a huge chance one won’t run into conflict in creating the same functions or properties.
You also have the flexibility of
re-naming the functions publicly just like we re-named
addAnimal function to
add. The downside here is the inability to override the created functions from an outside environment.
removeAnimal function can’t be overridden from outside without depending on the private property
4. Singleton pattern
As interesting as the above patterns are, they can’t be used in scenarios where only one instance is needed. Let’s take a look at database connection. You can’t keep on creating an instance of database when it’s already created. You either create a new instance when it’s closed or stop the ongoing instance to create a new one.
Singleton pattern ensures the instance of an object is only created once. It’s also known as the strict pattern. A downside to this pattern is that it is difficult to test. There are hidden dependencies objects, which are difficult to single out in order to test:
DatabaseConnection object will keep returning
1 because the instance was created only once.
5. Factory pattern
This pattern ensures objects are created with some sort of generic interface. We can specify the type of object we want to create from
interface object. Let’s assume we want to handle users payment using multiple vendors(Vendor A, Vendor B … Vendor n). The goal of each vendor is to ensure payment is successfully carried out.
More great articles from LogRocket:
- Don't miss a moment with The Replay, a curated newsletter from LogRocket
- Learn how LogRocket's Galileo cuts through the noise to proactively resolve issues in your app
- Use React's useEffect to optimize your application's performance
- Switch between multiple versions of Node
- Discover how to animate your React app with AnimXYZ
- Explore Tauri, a new framework for building binaries
- Compare NestJS vs. Express.js
In this kind of scenario, the
Factory pattern is our best bet. We won’t have to overthink how payment will be carried out irrespective of the vendor being used at a particular time.
Factory pattern provides an interface where we can specify the type of vendor we want to use in handling payment at each point in time:
Vendor A setting up configuration using username: test and password: 1234 Payment for service $12 is successful using Vendor A
............................................................ Vendor B setting up configuration using username: testTwo and password: 4321 Payment for service $50 is successful using Vendor B
factory pattern snippet above, we have two vendors (
A and B). The client interfacing with
VendorFactory need not to worry about which method to call when switching between
There is no point in using factory pattern if we don’t really want to create multiple instances of the same object. It would rather make the whole solution more complex.
6. Observer pattern
Observer pattern is useful in cases where an object needs to communicate with some sets of other objects at the same time. Imagine you need to sync an update across many components due to some changes.
Observer pattern prevents unnecessary push and pull of events across states. It notifies the modules involved by modifying the current state of the data:
Let’s take a look at an example to demonstrate observer pattern:
A user types a random number in an input field and the number gets modified and is displayed on two different documents.
This can also be achieved in AngularJS using two-way binding, which makes use of
Observer pattern under the hood:
<body style="text-align: center; margin-top: 40px;"> <input type="number" class="number-input" > <br> <br> <small>Number multiplied by 2</small> <p class="document-one">0</p> <button id="subscribe-one">Subscribe</button> <button id="unsubscribe-one">UnSubscribe</button> <br> <br> <small>Number multiplied by 4</small> <p class="document-two">0</p> <button id="subscribe-two">Subscribe</button> <button id="unsubscribe-two">UnSubscribe</button> </body>
Let’s interact with the elements we created to demonstrate observer pattern.
The observable container (
observerContainer) handles how
events are stored, retrieved and removed.
A view where the user gets to add a random number which is displayed on
documentOne(number displayed is multiplied by two) and
documentTwo (number displayed is multiplied by four).
Also, there are
unsubscribe buttons to modify the state of each document to display the modified random number.
The first set of two buttons (subscribe and unsubscribe) updates the display on the documents (
<p></p>), the displayed is altered by removing the update operation from the
observerContainer by clicking the unsubscribe button.
The same operation is applicable to the next two buttons (subscribe and unsubscribe):
This is a demo on how we demonstrated the use of observer pattern. The source code can also be found here.
If you need to update your view on a simple project, you may want to consider using an observer pattern rather than using a framework.
One of the downsides in observer pattern is difficulty in testing for different scenarios.
Debugging code is always a tedious task. But the more you understand your errors the easier it is to fix them.