A case in point

Let’s say we want to design a game where the bike can be configured by the player, with or without a front frame, a back seat, a light, a gear shift, etc.

As a general rule, we would define each part configuration as a class, then add an accessory and inherit it. But if so, we need to define a lot of classes, and there will be a lot of inheritance, making our code very redundant.

We wanted to add functionality to the bike class dynamically in a simpler way.

The role of decorator patterns

The decorator pattern is a way to dynamically assign responsibilities to an object during program execution without changing the object itself.

The decorator pattern is much lighter than inheritance.

Implement bicycle configuration using decorator pattern

First we implement the bike class

function Bicycle() {
	//setting......
}
Bicycle.prototype.func = function(){
	console.log("The function of the bicycle");
}
Copy the code

Later, we designed different functions into different packaging classes, such as lamp, frame and backseat. Each packaging class enhanced the function function of the original class, so as to achieve the function of inheritance.

function LightingBicycle(bicycle) {
	this.bicycle = bicycle;
}
LightingBicycle.prototype.func = function(){
	this.bicycle.func();
	console.log("Light");
}
function FrameBicycle(bicycle) {
	this.bicycle = bicycle;
}
FrameBicycle.prototype.func = function(){
	this.bicycle.func();
	console.log("Place");
}
function SeatBicycle(bicycle) {
	this.bicycle = bicycle;
}
SeatBicycle.prototype.func = function(){
	this.bicycle.func();
	console.log("Carry");
}
Copy the code

One last quick test

var bicycle = new Bicycle();
bicycle = new LightingBicycle(bicycle);
bicycle = new FrameBicycle(bicycle);
bicycle = new SeatBicycle(bicycle);
bicycle.func();
/ / print
// The function of bicycle
/ / glow
/ / place
/ / pick up
Copy the code

Above we simply realized the class packaging, and each new function is a wrapper class, we can enhance according to the needs, very convenient. In addition to decorating classes, we can also decorate functions.

Decoration of functions

When we want to enhance the function, we usually make direct changes to the function, but if the function is very cluttered and bulky, we can consider using decoration to enhance it.

Continuing with the bike example, if we simply wanted to enhance the func functionality, we could reference it using another method and then override it

function Bicycle() {
	//setting......
}
Bicycle.prototype.func = function(){
	console.log("The function of the bicycle");
}
var _func = Bicycle.prototype.func;
Bicycle.prototype.func = function(){
	_func();
	console.log("New features");
}

Copy the code

Since other variables are used to refer to functions, we need to consider the reference to this here.

For example, we want to enhance the document.getelementById method

var _getElementById = document.getElementById; 
document.getElementById = function( id ){ 
 alert (1); 
 return _getElementById( id ); / / (1)
} 
Copy the code

When this code executes, it will throw an error, because when we use getElementById, this refers to document, and when we use a new variable to refer to it, this already refers to window, so to use it correctly, We need to bind this to the enhanced method call.

var _getElementById = document.getElementById; 
 document.getElementById = function(){ 
 alert (1); 
 return _getElementById.apply( document.arguments ); 
 } 
Copy the code

Decorators provided by ES7

The decorator methods above are actually rarely used nowadays, as ES7 provides very convenient and powerful decorator methods.

To configure decorators, see blog.csdn.net/weixin_4276…

Class decorator

@testable
class MyTestableClass {
  // ...
}

function testable(target) {
  target.isTestable = true;
}

MyTestableClass.isTestable // true
Copy the code

In the code above, @testable is a testable testable testable testable testable device. It modifies the behavior of the MyTestableClass class by adding the static attribute isTestable to it. Testable functions target parameters from the MyTestableClass class itself.

Basically, the decorator behaves like this.

@decorator
class A {}

/ / is equivalent to

class A {}
A = decorator(A) || A;
Copy the code

That is, a decorator is a function that processes a class. The first argument to the decorator function is the target class to decorate.

If one argument is not enough, you can wrap another function around the decorator.

function testable(isTestable) {
  return function(target) {
    target.isTestable = isTestable;
  }
}

@testable(true)
class MyTestableClass {}
MyTestableClass.isTestable // true

@testable(false)
class MyClass {}
MyClass.isTestable // false
Copy the code

Note that the decorator changes the behavior of the class at compile time, not run time. This means that the decorator can run the code at compile time. That is, decorators are essentially functions that are executed at compile time.

Method decorator

Object.defineProperty

Decorators can decorate not only classes but also class properties.

Object.defineproperty () defines a new property on an Object or modifies an existing property of an Object and returns the Object.

Syntax: Object.defineProperty(obj, prop, Descriptor)

  • Obj: object to operate on
  • Prop: Name of the property to be defined or modified
  • Descriptor: Attribute descriptor to be defined or modified
  • Return value: The object passed to the function
class Person {
  @readonly
  name() { return `The ${this.first} The ${this.last}`}}Copy the code

In the code above, the decorator readonly decorates the name method of the “class”.

The decorator function readonly takes a total of three arguments.

  • The first argument is the class’s prototype object. The decorator is meant to “decorate” an instance of the class, but the instance has not yet been generated, so only the prototype can be decorated (as opposed to the class’s decoration, in which case the target argument refers to the class itself).
  • The second parameter is the name of the property to decorate
  • The third argument is the description object of the property
function readonly(target, name, descriptor){
  The original value of the // Descriptor object is as follows
  / / {
  // value: specifiedFunction,
  // enumerable: false,
  // configurable: true,
  // writable: true
  // };
  descriptor.writable = false;
  return descriptor;
}

readonly(Person.prototype, 'name', descriptor);
/ / similar to
Object.defineProperty(Person.prototype, 'name', descriptor);
Copy the code

Decorators can do a lot of things. You can read core-decorators.js, a third-party module that provides several common decorators to better understand decorators

Note: Decorators can only be used for classes and methods of classes, not functions, because function promotion exists.

Reference: segmentfault.com/a/119000001…

The last

Decorator pattern is dynamically adding more functionality to an existing function of a way to put each to the function of decoration in a separate function, and then packed in the function to decoration function object, therefore, when you need to perform special behavior, the calling code can selectively according to need, in order to use objects decorative function to packaging. The advantage is that the core responsibility of the class (function) is separated from the decoration function, reducing a lot of unnecessary code.