Detailed explanation of decorator mode of Javascript design mode

  • 2021-07-12 04:35:33
  • OfStack

1. Preface:

Decorator pattern (Decorator Pattern): Dynamic extension of object function without changing the original class and inheritance, and a new object with the same interface as the original object is realized by wrapping an object.

Features of the decorator pattern:

1. Add functions without changing the original structure of the original object.

2. The decorative object and the original object have the same interface, which enables customers to use the decorative object in the same way as the original object.

3. The decorative object contains the reference of the original object, that is, the decorative object is the real wrapped object of the original object.

2. Detailed explanation of Javascript decorator mode:

Description:

In decorator mode, additional functions can be dynamically added to objects at runtime. This can be a challenge when dealing with static classes. In Javascript, because objects are mutable, the process of adding functionality to objects is not a problem in itself.

One of the more convenient features of the decorator pattern is the customizable and configurable nature of its expected behavior. You can start with ordinary objects that have only 1 basic functionality, then choose from the pool of available decoration resources which functionality you need to enhance ordinary objects, and decorate them in order, especially when the order of decoration is important.

One of the ways to implement the decorator pattern is to make each decorator an object that contains methods that should be overloaded. Each decorator actually inherits the object that has been enhanced by the previous decorator. Each decorative method calls the same method on the "inherited object" and gets its value, in addition, it continues to perform 1 operation.

Start with Example 1:


// Classes (functions) to be decorated 
function Macbook() {
 this.cost = function () {
  return 1000;
 };
} 
// Calculate the packing cost of goods 
function PackagingFee(macbook) {
 this.cost = function () {
  return macbook.cost() + 75;
 };
}
// Calculate the freight of goods 
function Freight(macbook) {
 this.cost = function () {
  return macbook.cost() + 300;
 };
} 
// Calculate the insurance cost of goods 
function Insurance(macbook) {
 this.cost = function () {
  return macbook.cost() + 250;
 };
}
//  Usage 
var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
console.log(myMacbook.cost());//1625

Let's simply analyze the above code. In the above code, 1 defines 4 functions (including 1 function to be decorated and 3 functions to be decorated).

Then, a variable myMacbook is declared to point to the Insurance object from new, the formal parameters of the Insurance object point to the Freight object from new, the formal parameters of the Freight object point to the PackagingFee object from new, and the formal parameters of the PackagingFee object point to the Macbook object from new.

Next, call the cost method of myMacbook. From the above analysis, we can conclude that the values of myMacbook. cost () are equal to cost method +250 of Freight object, cost method of PackagingFee object is equal to cost method +300 of PackagingFee object, and cost method of PackagingFee object is equal to cost method +75 of Macbook object.

So the final result is: the value of myMacbook. cost () = 250 + (300 + (75 + 1000)) = 1625.


//  Usage 
var myMacbook = new Insurance(new Freight(new PackagingFee(new Macbook())));
console.log(myMacbook.cost());//1625 
// The above code is equivalent to the following split code, maybe you can see the logic before and after the split code 
var macbook = new Macbook();
var package = new PackagingFee(macbook);
var freight = new Freight(package);
var myMacbook = new Insurance(freight);
// Of course, if you don't want to declare so many variables ( macbook , package , freight ), only 1 A variable is also possible 
var macbook = new Macbook();
macbook = new PackagingFee(macbook);
macbook = new Freight(macbook);
var myMacbook = new Insurance(macbook);

Look at Example 2 again:


function ConcreteClass() {
 this.performTask = function () {
  this.preTask();
  console.log('doing something');
  this.postTask();
 };
}
function AbstractDecorator(decorated) {
 this.performTask = function () {
  decorated.performTask();
 };
}
function ConcreteDecoratorClass(decorated) {
 this.base = AbstractDecorator;
 this.base(decorated);// add performTask method
 decorated.preTask = function () {
  console.log('pre-calling..');
 };
 decorated.postTask = function () {
  console.log('post-calling..');
 };
}
var concrete = new ConcreteClass();
var decorator1 = new ConcreteDecoratorClass(concrete);
decorator1.performTask();
//pre-calling..
//doing something
//post-calling..

Example 2 is actually very similar to Example 1. Let's analyze it briefly. First, three functions are defined in Example 2, then two variables concrete and decorator1 are declared, and finally the performTask method of decorator1 is called.

At first glance, it seems that there is no performTask method in ConcreteDecoratorClass. Let's first analyze the following two lines of code:


var concrete = new ConcreteClass(); // Declaration 1 Variable concrete Point new Come out ConcreteClass Object 
var decorator1 = new ConcreteDecoratorClass(concrete); // Declaration 1 Variable decorator1 Point new Come out ConcreteDecoratorClass Object and pass in the variable concrete As a formal parameter 

Then, let's analyze the code in ConcreteDecoratorClass function line by line:


this.base = AbstractDecorator; // Definition 1 Current objects ( decorator1 ) base Property and point to the function AbstractDecorator
this.base(decorated); // Call base Property, which is the function that calls the AbstractDecorator Function, passing in formal parameters decorated Formal parameter decorated Point new Come out ConcreteClass Object 

Having said that, it seems that there is performTask method in ConcreteDecoratorClass function, and the key point is to look at "this"!

this in ConcreteDecoratorClass function points to ConcreteDecoratorClass object from new (that is, it points to the same object as decorator1);

The key of this in AbstractDecorator function is to see which object calls this function, and this points to which object (from the code "this. base = AbstractDecorator; this. base (decorated);" We can see that ConcreteDecoratorClass object from new is calling AbstractDecorator function), so this in AbstractDecorator function points to ConcreteDecoratorClass object from new (also points to the same object as decorator1).

To sum up, we will find that in the above code, both this in ConcreteDecoratorClass function and this in AbstractDecorator function point to ConcreteDecoratorClass object from new.

So when we execute decorator1.performTask (), it continues to execute the code in the anonymous function (decorated. performTask ();) The decorated parameter in the anonymous function points to the ConcreteClass object from new and executes the performTask method of the object.

Finally, look at Example 3:


var tree = {};
tree.decorate = function () {
 console.log('Make sure the tree won\'t fall');
}; 
tree.getDecorator = function (deco) {
 tree[deco].prototype = this;
 return new tree[deco];
}; 
tree.RedApples = function () {
 this.decorate = function () {
  this.RedApples.prototype.decorate(); //  No. 1 7 Step: Execute the prototype first (in this case, Angel Of) decorate Method 
  console.log('Add some red apples'); //  No. 1 8 Step   Re-output  red
  //  Put this 2 Step action RedApples Adj. decorate Method 
 }
};
tree.BlueApples = function () {
 this.decorate = function () {
  this.BlueApples.prototype.decorate(); //  No. 1 1 Step: Execute the prototype first decorate Method, that is, tree.decorate()
  console.log('Put on some blue apples'); //  No. 1 2 Step   Re-output blue
  //  Put this 2 Step action BlueApples Adj. decorate Method 
 }
}; 
tree.Angel = function () {
 this.decorate = function () {
  this.Angel.prototype.decorate(); //  No. 1 4 Step: Execute the prototype first (in this case, BlueApples Of) decorate Method 
  console.log('An angel on the top'); //  No. 1 5 Step   Re-output angel
  //  Put this 2 Step action Angel Adj. decorate Method 
 }
};
tree = tree.getDecorator('BlueApples'); //  No. 1 3 Step: Will BlueApples Object assigned to tree At this time, in the parent prototype getDecorator Still available 
tree = tree.getDecorator('Angel'); //  No. 1 6 Step: Will Angel Object assigned to tree At this time, in the parent prototype of the parent prototype, getDecorator Still available 
tree = tree.getDecorator('RedApples'); //  No. 1 9 Step: Will RedApples Object assigned to tree
tree.decorate(); //  No. 1 10 Step: Execute RedApples Object's decorate Method 
//Make sure the tree won't fall
//Add blue apples
//An angel on the top
//Put on some red apples

Example 3 looks very complicated, but in fact the parsing logic is the same as the previous two examples 1. We can see that 1 in Example 3 declares five function expressions. Let's focus on the following code:


//tree.getDecorator('BlueApples') Return new Come out tree.BlueApples And assigns the object to an empty tree Object 
tree = tree.getDecorator('BlueApples'); //new Come out tree.BlueApples Prototype points to the instance object of  -->  Empty object tree 
//tree.getDecorator('Angel') Return new Come out tree.Angel Instance object of the (article in this line of code) 2 A tree It's already up there 1 After the line of code runs the result tree.BlueApples Instance object of) 
tree = tree.getDecorator('Angel'); //new Come out tree.Angel Prototype points to the instance object of  --> tree.BlueApples Instance object of 
//tree.getDecorator('RedApples') Return new Come out tree.RedApples Instance object of the (article in this line of code) 2 A tree It's already up there 1 After the line of code runs the result tree.Angel Instance object of) 
tree = tree.getDecorator('RedApples'); //new Come out tree.RedApples Prototype points to the instance object of  --> tree.Angel Instance object of 
// Call tree.decorate() , here's tree Already is new Come out tree.RedApples Instance object of. 
//tree.RedApples Object of the instance object of decorate The first in the attribute method 1 The line code is   " this.RedApples.prototype.decorate() " 
// Combined with the above analysis, the following prototype chain structure can be obtained: 
//this.RedApples.prototype --> tree.Angel;
//tree.Angel.prototype --> tree.BlueApples;
//tree.BlueApples.prototype -->  Empty object tree
tree.decorate();

After analysis, it is not difficult to know the final output result.

3. Others:

We can see that a lot of this is used in the decorator pattern case in this article. Friends who don't know much about this can move to "understand this in javascript in depth".

The case of this article suggests copying it and analyzing it line by line, so act quickly!


Related articles: