JS and Design Patterns - Chapter 3 ๐
devlazar
Posted on January 22, 2021
Table Of Contents
* ๐คINTRODUCTION
* ๐คUSER DECORATOR
* โCOFFEE SHOP DECORATOR
* โ WHY AND WHEN DO WE USE DECORATOR PATTERN?
* ๐ตDIFFERENT TYPES OF DECORATOR PATTERN
* ๐REACT EXAMPLE
* ๐THANK YOU
INTRODUCTION
Welcome my fellow coders! I hope you are having a great time. Today was a very productive day, let's finish this week strong and talk about another interesting Design Pattern, a guy responsible for dynamically adding behavior to the existing classes - ๐THE DECORATOR PATTERN
The decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically. Let me explain it by using examples.
JAVASCRIPT CODE IMPLEMENTATION
USER DECORATOR
var User = function(name) {
this.name = name;
this.say = function() {
log.add("User: " + this.name);
};
}
var DecoratedUser = function(user, street, city) {
this.user = user;
this.name = user.name; // ensures interface stays the same
this.street = street;
this.city = city;
this.say = function() {
log.add("Decorated User: " + this.name + ", " +
this.street + ", " + this.city);
};
}
// logging helper
var log = (function() {
var log = "";
return {
add: function(msg) { log += msg + "\n"; },
show: function() { alert(log); log = ""; }
}
})();
var user = new User("Kelly");
user.say();
var decorated = new DecoratedUser(user, "Broadway", "New York");
decorated.say();
log.show();
COFFEE SHOP DECORATOR STORY
Now imagine a coffee shop. The coffee shop only sells coffee. But, the clever manager figured out that they could earn an extra ๐ฐ by selling different coffee condiments separately. We can help them manage that. Let's see how we can use our Decorator Pattern in this case.
โ NOTE: PLEASE READ THE COMMENTS ๐ฉโ๐ป
//Constructor that will be decorated
function Coffee(desc) {
//the type of the copy
this.type = desc;
//the description that will be modified
this.description = desc;
/*
A function expression is very similar to
and has almost the same syntax as a function
declaration. The main difference between a function
expression and a function declaration
is the function name, which can be omitted
in function expressions to create anonymous functions
A function expression can be used as an Immediately
Invoked Function Expression
*/
this.cost = function () { return 1.99; };
this.desc = function () { return this.description; };
//A regular function
function type () { return this.type } ;
}
//We are going to "decorate" our coffee with whip, Milk,
//Soy or whatever you want, you just need to add another
//condiment function
//which is going to change the price and the description that
//we see at the end
//Decorator 1
function Whip(houseBlend){
var hbCost = houseBlend.cost();
var hbDesc = houseBlend.desc();
houseBlend.desc = function(){
return hbDesc + ", Whip";
};
houseBlend.cost = function(){
return hbCost + .09;
};
}
//Decorator 2
function Milk(houseBlend){
var hbCost = houseBlend.cost();
var hbDesc = houseBlend.desc();
houseBlend.desc = function(){
return hbDesc + ", Milk";
};
houseBlend.cost = function(){
return hbCost + .1;
};
}
//Decorator 3
function Soy(houseBlend){
var hbCost = houseBlend.cost();
var hbDesc = houseBlend.desc();
houseBlend.desc = function(){
return hbDesc + ", Soy";
};
houseBlend.cost = function(){
return hbCost + .12;
};
};
//We create a brand new coffee object instance
//for example Espresso (type="Espresso", description="Espresso")
let coffee = new Coffee("Espresso");
//Double milk decorator
Milk(coffee);
Milk(coffee);
//A whip
Whip(coffee);
//And a soy? ๐ฒ
//(This ain't coffee anymore, I don't know what this is...๐)
Soy(coffee);
//fancy console log
console.log('%c%s', 'color: black; background: red; font-size: 24px;', "Coffee: " +coffee.desc()+` ${coffee.cost()}`);
let coffee2 = new Coffee("House Blend");
Milk(coffee2);
//A whip
Whip(coffee2);
console.log('%c%s', 'color: black; background: red; font-size: 24px;', "Coffee: " +coffee2.desc()+`, $${ coffee2.cost()}`);
//Output
//Coffee: Espresso, Milk, Milk, Whip, Soy, $2.4
In the previous coffee shop example, we saw that it is possible to apply multiple decorators, which can come in handy sometimes.
โ WHY AND WHEN DO WE USE DECORATOR PATTERN?
Decorators use a special syntax in JavaScript, whereby they are prefixed with an @ symbol and placed immediately before the code being decorated. (see tc39)
It's possible to use as many decorators on the same piece of code as you desire, and they'll be applied in the order that you declare them. Example:
@log()
@immutable()
class Example {
@time('demo')
doSomething() {
//
}
}
This is going to define a class and apply decorators - two to the class itself, and one to a property of a the class
- @\log - could log all access to the class
- @immutable - could make the class immutable - by calling Object.freeze()
- time - will record how long a method takes to execute and log this out with a unique tag.
Decorators can allow for a cleaner syntax for applying this kind of wrapper around your code. Whilst function composition is already possible, it is significantly more difficult - or even impossible - to apply the same techniques to other pieces of code.
๐ต DIFFERENT TYPES OF DECORATOR PATTERN
-
Class member decorators
Property decorators are applied to a single member in a class โ
whether they are properties, methods, getters, or setters. This
decorator function is called with three parameters:- target - the class that the member is on.
- name - the name of the member in the class.
- descriptor - the member descriptor. This is essentially the object that would have been passed to Object.defineProperty.
The classic example used here is @readonly.
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
- Class decorators Class decorators are applied to the entire class definition all in one go. The decorator function is called with a single parameter which is the constructor function being decorated. In general, these are less useful than class member decorators, because everything you can do here you can do with a simple function call in exactly the same way. Anything you do with these needs to end up returning a new constructor function to replace the class constructor.
๐ REACT EXAMPLE
React makes a very good example because of the concept of Higher-Order Components. These are simply React components that are written as a function, and that wrap around another component. These are ideal candidates for use as a decorator because there's very little you need to change to do so. For example. the react-redux library has a function, connect. That's used to connect a React component to a Redux store.
In general, this would be used as follows:
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
However, because of how the decorator syntax works, this can be replaced with the following code to achieve the exact same functionality:
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
Decorators, especially class member decorators provide a very good way of wrapping code inside a class in a very similar way to how you can already do so for freestanding functions.
Some real-world examples:
๐ THANK YOU FOR READING!
Please leave the comment, tell me about you, about your work, comment your thoughts, connect with me via Twitter or LinkedIn.
Let this year be your year, let this year be our year. Until the next typing...
Have a nice time!
Posted on January 22, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.