June 11th, 2022
When developing applications in JavaScript it's not uncommon to be encountering redundant code that make us question its positive impact in value. Redundancy is a common practice that developers often try to avoid doing because too much redundancy easily leads to unpleasant development experiences like increases in code size as well as decreases in code maintainability.
One great way to solve this issue is the Facade Design Pattern. In this post we will be going over the Facade and how it is an effective pattern to solve issues that are similar to this.
In addition to reducing redundancy the facade pattern also does a good job at defining a higher level interface that unify several interfaces to into more elegant interfaces with the intention of simplifying things more. It's commonly used to wrap unnecessarily complex parts of code into a simpler structure so that the client isn't responsible for steps they don't care about.
Let's pretend we are building a simple game and one of our first implementation of something will be a definition of a Human
class. This human class will be expected by other parts of our code to contain several body parts as properties: Eye
, Ear
, Arm
, Leg
, Feet
, Nose
, Mouth
, Neck
, and Stomach
:
class Human {
constructor(opts = {}) {
this.leftEye = opts.leftEye
this.rightEye = opts.rightEye
this.leftArm = opts.leftArm
this.rightArm = opts.rightArm
this.leftFoot = opts.leftFoot
this.rightFoot = opts.rightFoot
this.leftEar = opts.leftEar
this.rightEar = opts.rightEar
this.nose = opts.nose
this.mouth = opts.mouth
this.neck = opts.neck
this.stomach = opts.stomach
}
}
class Eye {}
class Ear {}
class Arm {}
class Leg {}
class Feet {}
class Nose {}
class Mouth {}
class Neck {}
class Stomach {}
Let's now define a Profile
class which has a method for setting its profile character called setCharacter
:
class Profile {
setCharacter(character) {
validateCharacter(character)
this.character = character
}
}
In a real world scenario of course it would be dozens of times longer than this so just keep in mind that we are focusing solely on the pattern and the problem it solves by presenting only the relative code.
We included a validateCharacter
in the beginning of our setCharacter
function because it's a necessary component of any software to validate constructed objects to ensure that errors don't occur unknowingly. This also happens to be a great setup to demonstrate the Facade.
Here is the implementation:
function validateCharacter(character) {
if (!character.leftEye) throw new Error(`Missing left eye`)
if (!character.rightEye) throw new Error(`Missing right eye`)
if (!character.leftEar) throw new Error(`Missing left ear`)
if (!character.rightEar) throw new Error(`Missing right ear`)
if (!character.nose) throw new Error(`Missing nose`)
if (!character.mouth) throw new Error(`Missing mouth`)
if (!character.neck) throw new Error(`Missing neck`)
if (!character.stomach) throw new Error(`Missing stomach`)
}
So inside our validateCharacter
call it proceeds to check every part of the human body and throws an error if at least one body part is missing.
Let's try to use our code as if we were the client:
const bob = new Human()
const bobsProfile = new Profile()
bobsProfile.setCharacter(bob)
Running the code will result with an error:
Error: Missing left eye
So how does the client fix this? Easy! They just need to construct every single body part and be responsible for making them exist on the instance:
const bobsLeftEye = new Eye()
const bobsRightEye = new Eye()
const bobsLeftEar = new Ear()
const bobsRightEar = new Ear()
const bobsNose = new Nose()
const bobsMouth = new Mouth()
const bobsNeck = new Neck()
const bobsStomach = new Stomach()
const bobsLeftArm = new Arm()
const bobsRightArm = new Arm()
const bobsLeftLeg = new Leg()
const bobsRightLeg = new Leg()
const bobsLeftFoot = new Feet()
const bobsRightFoot = new Feet()
bob.leftEye = bobsLeftEye
bob.rightEye = bobsRightEye
bob.leftEar = bobsLeftEar
bob.rightEar = bobsRightEar
bob.nose = bobsNose
bob.mouth = bobsMouth
bob.neck = bobsNeck
bob.stomach = bobsStomach
bob.leftArm = bobsLeftArm
bob.rightArm = bobsRightArm
bob.leftLeg = bobsLeftLeg
bob.rightLeg = bobsRightLeg
bob.leftFoot = bobsLeftFoot
bob.rightFoot = bobsRightFoot
Now our code runs without errors. If you went along hands-on however you might have noticed that we only cared about creating the character and profile. Notice we encountered some issues here:
"bob"
throughout our code.Now lets make our Facade and define how it will solve these issues for clients:
class Profile {
setCharacter(character) {
if (!character.leftEye) character.leftEye = new Eye()
if (!character.rightEye) character.rightEyye = new Eye()
if (!character.leftEar) character.leftEar = new Ear()
if (!character.rightEar) character.rightEar = new Ear()
if (!character.nose) character.nose = new Nose()
if (!character.mouth) character.mouth = new Mouth()
if (!character.neck) character.neck = new Neck()
if (!character.stomach) character.stomach = new Stomach()
this.character = character
}
}
const bob = new Human()
const bobsProfile = new Profile()
bobsProfile.setCharacter(bob)
Our Profile
becomes the Facade itself and effectively encapsulates every implementation of body parts as a fallback so that the client code only needs to focus on the interface provided by the Profile
.
We not only give them the option to skip the unnecessary steps to construct and set each body part, we also give them the option to be fully in control of that if they wanted to on their own.
Also, notice that the user of our code is tightly coupled to the interface that Profile
exposes.
Here is an example taken from a gist that implements a facade. The intent of the pattern in this example is to encapsulate multiple operations into one single call. In a business logic perspective this could represent an interface for client code to perform an online e-book purchase where the client doesn't really care about how the book is purchased--it only needs a "save" and "send" behavior for their users:
var module = (function () {
'use strict'
// Private Method
var book = {
get: function () {
// Find book with the id/index
console.log('Getting Book Info...')
},
set: function (bID, uID) {
// use bookID(bID) and userID(uID) to set book to user
},
download: function () {
console.log('downloading')
},
mailing: function () {
console.log('Thank You for Subscribing.')
},
}
return {
// Create the facade for running whole list of process
facade: function (data) {
book.set(data.id, data.userID)
book.get()
if (data.download) book.download()
if (data.mailing) book.mailing()
},
}
})()
The client code than uses this interface and doesn't need to worry about the implementation details:
module.facade({
id: 2,
userID: 123123,
download: true,
mailing: true,
})
Let's take grocery stores as an example. When you buy groceries and the cashier informs you of the price of the goods you are paying for, you have the option of paying in cash or credit card. Although these two methods of payment are different they both ultimately achieve the same goal. Nowadays since the covid19 pandemic a new popular method of payment is the tap to pay which again is different in the sense that we no longer require to swipe or exchange some form of money.
This is the facade pattern where it provides a new way to pay (a new interface) to users that do the same thing.
One of my top most favorite open sourced projects is ts-morph. This library serves as a wrapper around the TypeScript compiler API to provide an "easier way to programmatically navigate and manipulate TypeScript and JavaScript code". The original TypeScript compiler API can easily become complex, so ts-morph encapsulates these complexities and exposes an entirely new easy-to-use interface to the client code to use instead. I am a frequent user of ts-morph so I can vouch on a personal level that this has made development with the TypeScript API much more easier.
Sometimes the facade design pattern can be mistaken for the Adapter design pattern. But the difference is that the Facade may expose an entirely new interface for the client to use whereas the Adapter's intent is to be backwards compatible with previous interfaces when seeking to extend with newer properties and/or behavior.
In the facade, the client code is given an interface to work with that represents an entire system of objects (which can contain new copies of identical objects) while the client in the flyweight pattern is given an interface to produce objects that intend to be shared when identical which is an effective approach to preserve memory.
And that concludes the end of this post! I hope you found this to be valuable and look out for more in the future!
Tags
© jsmanifest 2023