What is decorator pattern

When we take a photo and want to post it on our moments, many people choose to filter it. The same photo, with different filters, makes for a different experience. The decorator pattern is actually applied here: the photo is decorated with a filter. Dynamically add functionality (filters) to an object (photo) without changing it.

One thing to note: Due to the dynamic nature of the JavaScript language, it is very easy to change an object (functions in JavaScript are first-class citizens). However, we want to avoid directly rewriting a function, which can make the code less maintainable, extensible, or even contaminate other businesses.

What is the AOP

We must be familiar with “washing hands before meals and gargling after meals”. The slogan is actually an example of AOP in life: the action of eating is equivalent to cutting point, and we can insert other actions such as washing hands before and after the cutting point.

Aspect-oriented Programming (AOP) : Section-oriented Programming, is a supplement to OOP. AOP can be used to isolate each part of the business logic, but also can isolate the business irrelevant functions such as log reporting, exception processing, so as to make the coupling degree between each part of the business logic reduced, improve the reuse of business irrelevant functions, also improve the efficiency of development.

In JavaScript, we can implement AOP through the decorator pattern, but the two are not the same dimensional concept. AOP is a programming paradigm, while decorator is a design pattern.

ES3 decorator implementation

With the decorator pattern and AOP concepts in mind, let’s write es3-compatible code to implement the Decorator pattern:

/ / function
var takePhoto =function(){
    console.log('Take a picture');
}
// Define an AOP function
var after=function( fn, afterfn ){ 
    return function(){
        let res = fn.apply( this.arguments ); 
        afterfn.apply( this.arguments );
        returnres; }}// Decorates the function
var addFilter=function(){
    console.log('Add filter');
}
// Decorate the original function with a decorator function
takePhoto=after(takePhoto,addFilter);

takePhoto();
Copy the code

This allows us to implement the extraction photo and filter logic, which can also be added via the AOP function After if automatic upload is needed later.

ES5 decorator implementation

Object.defineproperty was introduced in ES5 to make it easier to add attributes to objects:

let takePhoto = function () {
    console.log('Take a picture');
}
// Add the attribute after to takePhoto
Object.defineProperty(takePhoto, 'after', {
    writable: true.value: function () {
        console.log('Add filter'); }});// Add the attribute before to takePhoto
Object.defineProperty(takePhoto, 'before', {
    writable: true.value: function () {
        console.log('Turn on the camera'); }});// Packing method
let aop = function (fn) {
    return function () {
        fn.before()
        fn()
        fn.after()
    }
}

takePhoto = aop(takePhoto)
takePhoto()
Copy the code

Decorator implementation based on prototype chains and classes

We know that in JavaScript, functions and classes have their own prototypes, which can be easily extended dynamically through prototype chains. Here’s how to write based on prototype chains:

class Test {
    takePhoto() {
        console.log('photos'); }}// after AOP
function after(target, action, fn) {
    let old = target.prototype[action];
    if (old) {
        target.prototype[action] = function () {
            let self = this; fn.bind(self); fn(handle); }}}// Use AOP functions to decorate primitives
after(Test, 'takePhoto'.() = > {
    console.log('Add a filter');
});

let t = new Test();
t.takePhoto();
Copy the code

Implement decorators using ES7 decorators

A proposal for the @decorator decorator has been introduced in ES7. See Nguyen Yi-feng’s article. A modifier is a function that modifies the behavior of a class. Babel transcoder is currently supported. Note that modifiers can only decorate classes or class attributes or methods. Please refer to MDN Object.defineProperty for specific differences between the three. TypeScript is implemented differently: TypeScript Decorator.

Next we decorate methods with decorators:

function after(target, key, desc) {
    const { value } = desc;
    desc.value = function (. args) {
        let res = value.apply(this, args);
        console.log('Add filter')
        return res;
    }
    return desc;
}

class Test{
    @after
    takePhoto(){
        console.log('photos')}}let t = new Test()
t.takePhoto()

Copy the code

As you can see, the code that uses the decorator is very clean.

Scenario: Performance report

The Decorator pattern can be used in many scenarios. A typical scenario is to record the performance data of an asynchronous request and report it:

function report(target, key, desc) {
    const { value } = desc;
    desc.value = async function (. args) {
		let start = Date.now();
        let res = await value.apply(this, args);
		let millis = Date.now()-start;
    	// Report code
    	return res;
    }
    return desc;
}

class Test{
	@report
    getData(url){
    / / the fetch code}}let t = new Test()
t.getData()
Copy the code

This way the code decorated with @report will report the time consumed by the request. Extending or modifying the report function does not affect the business code and vice versa.

Scenario: Exception handling

We can do simple exception handling to the original code without intrusive modifications:

function handleError(target, key, desc) {
    const { value } = desc;
    desc.value = async function (. args) {
        let res;
        try{
            res = await value.apply(this, args);
        }catch(err){
            // Exception handling
            logger.error(err)
        }
        return res;
    }
    return desc;
}

class Test{
	@handleError
    getData(url){
        / / the fetch code}}let t = new Test()
t.getData()
Copy the code

As you can see from the two examples above, the definition of a decorator is simple, yet very powerful.

summary

We implement the Decorator pattern step by step through higher-order functions, prototype chains, Object.defineProperty, and @decorator, respectively. Here’s a review:

  • Decorator mode is great for attaching non-business related functions to business code (such as log reporting), much like filters to photos;
  • Decorator mode is great for painlessly extending other people’s code (you often have to take over other people’s projects)

Some of you may think decorator patterns are similar to vue mixins, but they are both the “open-closed principle” and the “single responsibility principle”.

Attach code Jsbin address:

  • ES3
  • ES5
  • Prototype chain
  • ES7