Design patterns are the basis of any good software. JavaScript programs are no exception.
In this article, we’ll look at the adapter and facade patterns.
Adapter
The adapter pattern is useful for creating a consistent interface for another object.
The object we’re creating an interface for doesn’t fit with how it’s used.
Therefore, we create an adapter so that we can use them easily.
It also hides the implementation so that we can use it easily and with low coupling.
For instance, we can create an adapter object as follows:
const backEnd = {
setAttribute(key, value) {
//.,,
},
getAttribute() {
//...
}
}
const personAdapter = {
setFirstName(name) {
backEnd.setAttribute('firstName', name);
},
setLastName(name) {
backEnd.setAttribute('lastName', name);
},
getFirstName() {
backEnd.getAttribute('firstName');
},
getLastName() {
backEnd.getAttribute('lastName');
}
}
We created an adapter object called personAdapter
so that we can use backEnd
to set all the attributes.
The adapter has an interface that people can understand more easily since the method names are explicit.
Other objects or classes can use the object to manipulate the attributes.
We can use adapters to create interfaces that we want to expose to the public any way we want.
Inheriting Objects with Adapters
With adapters, we can also create an interface for multiple classes or objects.
For instance, we can write:
const backEnd = {
setAttribute(key, value) {
//.,,
},
getAttribute() {
//...
}
}
const parentAdapter = {
//...
getAdapterName() {
//...
}
}
const personAdapter = Object.create(parentAdapter);
personAdapter.setFirstName = (name) => {
backEnd.setAttribute('firstName', name);
};
personAdapter.setLastName = (name) => {
backEnd.setAttribute('lastName', name);
};
personAdapter.getFirstName = () => {
backEnd.getAttribute('firstName');
};
personAdapter.getLastName = () => {
backEnd.getAttribute('lastName');
}
In the code above, we created personAdapter
with Object.create
, which allows us to inherit from another parent object.
So we get the getAdapterName
method from the parentAdapter
prototype object.
Then we add our own methods to personAdapter
.
Now we can get inherited methods from another object plus the methods in our own adapter object in the adapter.
Likewise, we can do the same with the class syntax.
For instance, we can create an adapter class instead of an object.
We can write:
const backEnd = {
setAttribute(key, value) {
//.,,
},
getAttribute() {
//...
}
}
class ParentAdapter {
//...
getAdapterName() {
//...
}
}
class PersonAdapter extends ParentAdapter {
setFirstName(name) {
backEnd.setAttribute('firstName', name);
}
setLastName(name) {
backEnd.setAttribute('lastName', name);
}
getFirstName() {
backEnd.getAttribute('firstName');
}
getLastName() {
backEnd.getAttribute('lastName');
}
}
const personAdapter = new PersonAdapter();
console.log(personAdapter);
The code above has a PersonAdapter
and ParentAdapter
classes instead of objects.
We used the extends
keyword to inherit members from ParentAdapter
.
This is another way we can inherit members from a parent entity.
Facade Pattern
We can use the facade pattern to create a class or object to hide the implementation of something complex behind it.
This is used to create an easy to use interface for the outside while keeping the complex implementation of something inside.
For instance, we can write:
const complexProduct = {
setShortName(name) {
//...
},
setLongName(name) {
//...
},
setPrice(price) {
//...
},
setDiscount(discount) {
//...
},
//...
}
const productFacade = {
setName(type, name) {
if (type === 'shortName') {
complexProduct.setShortName(name);
} else if (type === 'longName') {
complexProduct.setLongName(name);
}
}
//...
}
We created a productFacade
that has some methods that call various methods in the complexProduct
object.
This way, we can use the facade to simplify what we do, and also reduce the coupling to the complexProduct
object.
We let the productFacade
object communicates with the complexProduct
object.
It also serves to reduce coupling from complex objects.
In addition, a facade can also create an interface for something that won’t change as often as what’s behind it.
An interface that doesn’t change as often is good since more changes mean more risks of bugs.
Likewise, we can create a facade class that does the same thing as follows:
const complexProduct = {
setShortName(name) {
//...
},
setLongName(name) {
//...
},
setPrice(price) {
//...
},
setDiscount(discount) {
//...
},
//...
}
class ProductFacade {
setName(type, name) {
if (type === 'shortName') {
complexProduct.setShortName(name);
} else if (type === 'longName') {
complexProduct.setLongName(name);
}
}
//...
}
Then we can create an instance of ProductFacade
instead of using the object literal directly.
A facade can be used to manipulate one or more objects. That’s different from the adapter pattern, where we make an adapter for one object.
Conclusion
We can use the adapter pattern to hide the implementation of one object bu creating an interface that’s easier to work with to the outside.
The facade pattern lets us hide complex implementations behind one interface that can be used instead of one or more complex objects being the facade.