JavaScript lets us do a lot of things. It’s sometimes too forgiving in its syntax.
The organize our code, we should use some basic design patterns. In the article, we’ll look at decorators, facades, and proxies.
Decorator
Decorators let us add functionality to an object on the fly.
JavaScript has decorators now so that we can use them to create our own decorator and mutate objects on the fly.
For instance, we can create one as follows:
const foo = () => {
return (target, name, descriptor) => {
descriptor.value = "foo";
return descriptor;
};
};
class Foo {
@foo()
bar() {
return "baz";
}
}
const f = new Foo();
console.log(f.bar);
A decorator in JavaScript, it’s just a function that returns a function with the parameters target
, name
and descriptor
.
descriptor
is the property descriptor, so we can set the value
, writable
, and enumerable
properties for it.
Our foo
decorator transformed the value
property of it to the string 'foo'
.
So we get that Foo
instance’s bar
property becomes the string 'foo'
instead of a method.
JavaScript decorators are still experimental syntax, so we need Babel to use it.
The @babel/plugin-proposal-decorators
will let us use decorators in our JavaScript projects.
The function returned by the decorator function has 2 other parameters.
The target
is the constructor or class that the decorator is in.
The name
parameter is the string of the entity that it’s modifying.
We can use it to modify other entities in a class.
Facade
The facade is a simple pattern that lets us provide an alternative interface to an object.
We can use it to hide the complex implementation behind a facade.
This is good because we don’t want users of our code to know everything.
The facade is very simple and we don’t have to do much.
We can also use it to combine multiple operations that are called together into one.
For instance, if we have multiple methods that are called together, then they can be put together into one method.
We can implement the facade pattern as follows:
const adder = {
add() {
//...
},
}
const subtractor = {
subtract() {
//...
},
}
const facade = {
calc() {
adder.add();
subtractor.subtract();
}
}
We created a facade
object that combines the add
and subtract
methods.
The facade pattern is used to create an interface in front of multiple objects.
Also, we can use it to handle the intricacies of browsers.
Some browsers may not have the preventDefault
or stopPropgation
methods, so we may want to check that they exist before calling them:
const facade = {
stop(e) {
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
if (typeof e.stopPropagation === "function") {
e.stopPropagation();
}
//...
}
}
Now we call always call stop
and don’t have to worry about their existence since we check that they exist before calling them.
Proxy
The proxy pattern is where one object acts as an interface to another object.
The difference between proxy and facade patterns are that we can proxies create interfaces for one object.
But facades can be interfaces for anything.
We can create a proxy as follows:
const obj = {
foo() {
//...
},
bar() {
//...
},
}
const proxy = {
call() {
obj.foo();
obj.bar();
}
}
We use the proxy
to call both foo
and bar
in obj
.
This pattern is good for lazy initialization.
This is where we only initialize something when it’s used.
Instead of always running the initialization code, we run the initialization code only when needed.
Proxy As a Cache
Proxies can act as a cache. We can check the cache for the results before returning the result.
If we have it, then we return the result from the cache.
Otherwise, we generate the result, put it in the cache, and then return it.
For instance, we can write:
const obj = {
add() {
//...
}
}
const proxy = {
cache: {},
add(result) {
if (!cache[result]) {
cache[result] = obj.add();
//...
}
return cache[result];
}
}
Now we can do expensive operations only when needed instead of always doing it.
Conclusion
The decorator pattern can be used to change entities on the fly.
JavaScript has experimental decorators that let us do that.
Facades and proxies can both be used to hide the implementations of objects.
Proxies if for hiding one object, and facades can be used to hide more complex implementations.