Traditionally, structural patterns are described as the thing that helps simplify the process of object composition. Its purpose is to help identify the relationship between different objects, making sure that if something changes within the system, it doesn’t force the entire system to change with it.
Let’s begin with the adapter pattern.
Adapter Pattern
Imagine you have to build another cart solution for your boss. But there’s a caveat — you’re not allowed to change the original code that spits out the cart data. Why? Because no one knows how that change will impact the rest of the application. It’s a fragile piece of art that no one wants to touch, not yet, or ever. The situation is one of those moments where you just don’t have the time or mental space to question it. You’ll just have to accept that it’s going to be like this until you fully transition out of your legacy systems.
But the process of transitioning is going to take a long time and you don’t want your code to be too intertwined with the old code. What do you do?
In situations like these, the adapter pattern comes in handy.
The adapter pattern introduces an intermediary piece of code that makes two parts of a system compatible with one another. It also injects an element of lose coupling by keeping the two pieces of code separate. It means that you can write your code however you want without the need to take the other piece of code into consideration. Your adapter code will do the necessary translation and give you what you need and in whatever format you want.
When one side of the code changes, you only need to change the adapter for that particular part rather than both sides of the application.
So how exactly do you write an adapter pattern in JavaScript?
There’s no actual way to write it as such. It’s more a conceptual idea and the code itself is dependent on the situation you’re trying to bridge. It’s a process of abstracting the data you need from an external or 3rd party object. To do this, we often create an interface to represent our adapter and create the connections required between the two parts.
One thing to take note is that the adapter only has a single direction of dependency. The adapter only consumes what it aims to translates. In most cases, it’s the legacy API or 3rd party libraries. It doesn’t do anything else other than form a connection between two sides of an application. It should not contain any business logic.
Conventionally, an adapter sits in a utils
folder and then imported into a file when it's needed. However, it can also be a stand alone function.
Let’s take a look at the exported class
version first.
//your original cart data service
//this is just a hypothetical name
import { v4 as cart } from "cartServiceV4"
class CartServiceAdapter {
getCart(){
//some cart code
//using data from the imported cart service
//can transform the original output to suit your needs
return cart;
}
removeItemFromCart(){
//some cart code
return cart;
}
//etc
}
export default new CartService();
import
brings in the thing you want to translate. export
makes your CartServiceAdapter
available for usage by anything that imports it.
When you need the cart
data, you just need to import your adapter into your code using import
.