JS and Design patterns - Chapter 3 ๐Ÿš€

JS and Design patterns - Chapter 3 ๐Ÿš€

ยท

10 min read


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

Decorator

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 on the filter method, 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!

References: School notes... tc39 sitepoint