Analysis of JavaScript Design Patterns-Decorator Patterns

Keywords: Windows Javascript Programming JSON

Decorator Model
On the basis of not changing the object itself, add some additional responsibilities to the object dynamically during the running of the program

In traditional object-oriented languages, inheritance is often used to add functions to objects.
But inheritance has many shortcomings:

  • Superclass and subclasses are strongly coupled, and the change of superclasses results in the change of subclasses.
  • The inner details of superclasses are visible to subclasses, destroying encapsulation
  • When functional reuse is completed, a large number of subclasses may be created.

The first two points are well understood.
On the last point
For example, we define five classes for five different types of houses.
But we also need to add doors, windows and spray paint to every house.
If inheritance is used, the number of subclasses we need to add is: 5 x 3 = 15
It's very troublesome.
But if we can dynamically add doors, windows, and spray paint to each house object
So we only need these three categories

The way to dynamically add responsibilities to objects is the Decorator pattern.

Simple implementation

For our dynamic interpreting language JavaScript, it's easy to implement.
We'll do it step by step.

First of all, is the Decorator pattern not a dynamic addition of responsibilities, simple

var person = {
    name: 'payen',
    sex: 'male'
}
person.age = '20';

Of course, it's not that simple.
I said that at the top.
Decorator pattern is based on not changing the object itself.
And we changed the original object.
So let's go one step further.

Now let's assume we're developing a little game: Thunder
In the beginning, we used the worst airplane, and when we shot down the airplane, we ate props.
Increasing firepower NB
Eating a star can not only make ordinary bullets, but also shoot shots.
Eating one more can not only launch ordinary bullets and shotguns, but also track missiles.

var plane = {
    fire: function(){
        console.log('Bullet launch');
    }
}
plane.fire();
//Bullet launch
var fire1 = plane.fire;
var shot = function(){
    console.log('Scatter shot');
}
plane.fire = function(){
    fire1();
    shot();
}
plane.fire();
//Launch Bullets to Launch Bullets
var fire2 = plane.fire;
var track = function(){
    console.log('Launch tracking missile');
}
plane.fire = function(){
    fire2();
    track();
}
plane.fire();
//Launch Bullet Launch Bullet Launch Tracking Missile

In this way, the dynamic way of adding responsibilities to the object does not change the object itself.
Put one object into another
Formed a decorative chain, an aggregate object
The above shot s and track s are the decorators and decoration functions.
When the function executes, the request is forwarded to the next object in the chain

Functional Extension

In JavaScript, it is easy to extend attributes and methods to objects.
But it's not easy to extend additional functionality to functions unless you change the source code of the function.
But rewriting functions violates the open-closed principle

var foo = function(){
    console.log(1);
}
//Change to
var foo = function(){
    console.log(1);
    console.log(2);//increase
}

A common method is to cache function references and rewrite functions.

var foo = function(){
    console.log(1);
}
//Change to
var foo = function(){
    console.log(1);
}
var _foo = foo;
foo = function(){
    _foo();
    console.log(2);
}

But there are still problems in writing this way.

  • To maintain additional intermediate variables (_foo), if the decoration chain is too long, more and more intermediate variables will be maintained.
  • There may be hijacking of this

For this hijacking problem, see the following example

var getId = document.getElementById;
document.getElementById = function(ID){
    console.log(1);
    return getId(ID);
}
document.getElementById('demo');

This way the browser will report errors

Because when using document.getElementById
There is an internal reference to this, which is expected to point to document
But after getting the document.getElementById reference from getId
this points to window, and an error occurs.

In order for this to point to document correctly
We can make changes.

var getId = document.getElementById;
document.getElementById = function(ID){
    console.log(1);
    return getId.call(document, ID);
}
document.getElementById('demo');

But it's still troublesome.
We can achieve a perfect solution through AOP

AOP Decorative Function

First, let's give you a general idea of what AOP is.

AOP (Aspect Oriented Programming) Face-Oriented Programming
Extract some functions that are not related to the core business logic
Then it incorporates business logic module by "dynamic weaving"

Functions unrelated to business logic usually include log statistics, security control, exception handling, and so on.
The benefits are also obvious, ensuring the purity and cohesion of core business modules.
And other functional modules can be reused well.

First, we need to implement several functions.
One for front decoration and one for rear decoration

Function.prototype.before = function(beforeFunc){
    var that = this;
    return function(){
        beforeFunc.apply(this, arguments);
        return that.apply(this, arguments);
    }
}
Function.prototype.after = function(afterFunc){
    var that = this;
    return function(){
        var ret = that.apply(this, arguments);
        afterFunc.apply(this, arguments);
        return ret;
    }
}

Take the front decoration as an example
When calling before, save the reference to the original function first
Then return a proxy function
In this way, the extended function is executed before the original function is called.
And they share the same parameter list
Post-decoration is similar to pre-decoration except that the execution order is different.

If you don't like this way of polluting prototypes, you can also write this way.

var before = function(originFunc, beforeFunc){
    return function(){
        before.apply(this, arguments);
        return originFunc.apply(this, arguments);
    }
}
var after = function(originFunc, afterFunc){
    return function(){
        var ret = originFunc.apply(this, arguments);
        afterFunc.apply(this, arguments);
        return ret;
    }
}

Using this AOP approach, functions can be perfectly extended.

var foobar = function(x, y, z){
    console.log(x, y, z);
}
var foo = function(x, y, z){
    console.log(x/10, y/10, z/10);
}
var bar = function(x, y, z){
    console.log(x*10, y*10, z*10);
}
foobar = foobar.before(foo).after(bar);
foobar(1, 2, 3);
//0.1 0.2 0.3
//1 2 3
//10 20 30

Because the original function and the decorative function share a list of parameters
So we can use AOP to change function parameters.

var data = {
    width: '100px',
    height: '100px'
}
var demo = function(data){
    console.log(JSON.stringify(data));
}
demo = demo.before(function(data){
    data.color = 'red';
});
demo(data);
//{"width":"100px","height":"100px","color":"red"}

Last

Finally, the shortcomings of the decorator model
Nor is it perfect.

  • Decorative chains overlay functional scopes, which can cause performance problems if they are too long
  • If the attributes are saved on the original function, the attributes will be lost when the new function is returned.
var demo = function(){
    console.log(1);
}
demo.a = 123;
demo = demo.after(function(){
    console.log(2);
});
demo();
console.log(demo.a);
//undefined

Decorator pattern is very useful in development and framework development.
Suitable for Cooperative Strategy Model

Posted by Andrew W Peace on Tue, 26 Mar 2019 11:42:30 -0700