The way objects add responsibilities dynamically is called the decorator pattern. Decorator pattern allows you to dynamically add responsibilities to objects during program execution without changing the objects themselves. Decorator is a lighter, more flexible approach than inheritance. It’s a “pay-as-you-go” approach.

1. Emulates the decorator pattern of traditional object-oriented languages

  • Let’s say we’re writing a game in which we’re fighting planes. As experience increases, the planes we’re flying can be upgraded to more powerful planes. At first, these planes can fire normal bullets, at level 2 they can fire missiles, and at level 3 they can fire atomic bombs.
var Plane = function(){} 
Plane.prototype.fire = function(){ 
 console.log( 'Fire ordinary bullets' ); 
}
// Add two decoration classes, respectively missile and atomic bomb:
var MissileDecorator = function( plane ){ 
 this.plane = plane; 
} 
MissileDecorator.prototype.fire = function(){ 
 this.plane.fire(); 
 console.log( 'Launch a missile' ); 
} 

var AtomDecorator = function( plane ){ 
 this.plane = plane; 
} 
AtomDecorator.prototype.fire = function(){ 
 this.plane.fire(); 
 console.log( 'Launch an atomic bomb' ); 
}

var plane = new Plane(); 
plane = new MissileDecorator( plane ); 
plane = new AtomDecorator( plane ); 
plane.fire(); // Separate output: firing ordinary bullets, firing missiles, firing atomic bombs
Copy the code

This way of dynamically adding responsibility to an object does not really change the object itself, but puts the object into another object, which is referred to in a chain, forming an aggregate object. These objects all have the same interface (fire method), and when a request reaches an object in the chain, that object performs its own operation and then forwards the request to the next object in the chain.

Because the decorator object and the object it decorates have a consistent interface, they are transparent to the client using the object, and the decorator object does not need to know that it has been decorated. this transparency allows us to recursively nest any number of decorator objects.

2. JavaScript decorator

The JavaScript language makes it fairly easy to change objects on the fly. You can rewrite an object or a method of an object directly without using a “class” to implement the decorator pattern

var plane = { 
 fire: function(){ 
 console.log( 'Fire ordinary bullets'); }}var missileDecorator = function(){ 
 console.log( 'Launch a missile' ); 
} 
var atomDecorator = function(){ 
 console.log( 'Launch an atomic bomb' ); 
} 
var fire1 = plane.fire; 
plane.fire = function(){ 
 fire1(); 
 missileDecorator(); 
} 
var fire2 = plane.fire; 
plane.fire = function(){ 
 fire2(); 
 atomDecorator(); 
} 
plane.fire(); 
// Separate output: firing ordinary bullets, firing missiles, firing atomic bombs
Copy the code

3. Decorator functions

// The simplest and most crude way to add some functionality to a function is to rewrite the function directly, but this is the worst way to directly violate the open  closed principle:
var a = function(){ 
 alert (1); 
}
/ / to:
var a = function(){ 
 alert (1); 
 alert (2); 
}
Copy the code

A lot of times we don’t want to touch the original function, maybe the original function was written by another colleague and the implementation is very messy. Even in an old project, the source code for this function was hidden in a dark corner we didn’t want to touch. Now we need a way to add functionality to a function without changing its source code, which is exactly what the open  closed principle shows us.

var a = function(){ 
 alert (1); 
} 
var _a = a; 
a = function(){ 
 _a(); 
 alert (2); 
} 
a();
Copy the code

This code of course conforms to the open  closed principle. It is true that we did not modify the original window.onload code when adding new features, but there are two problems with this approach:

  • You have to maintain the intermediate variable _a, which may not seem like much, but if the function has a long decorative chain, or

    The number of intermediate variables increases as the number of functions that need decoration increases.

  • This is the hijacked problem

4. Decorate functions with AOP

First Function is given. The prototype. Before method and the Function prototype. After methods

Function.prototype.before = function (beforefn) {
  var __self = this // Save a reference to the original function
  return function () {// Returns a "proxy" function that contains both the original function and the new function
    // Execute the new function, and ensure that this is not hijacked. The parameters accepted by the new function are also passed to the original function, and the new function is executed before the original
    beforefn.apply(this.arguments)
    // Execute the original function and return the result of the original function, and ensure that this is not hijacked
    return __self.apply(this.arguments)}}Function.prototype.after = function (afterfn) {
  var __self = this
  return function () {
    var ret = __self.apply(this.arguments)
    afterfn.apply(this.arguments)
    return ret
  }
}
Copy the code
  • use
<html>
  <button id="button"></button>
  <script>
    Function.prototype.before = function (beforefn) {
      var __self = this
      return function () {
        beforefn.apply(this.arguments)
        return __self.apply(this.arguments)}}document.getElementById = document.getElementById.before(function () {
      alert(1)})var button = document.getElementById('button')
    console.log(button)
  </script>
</html>
Copy the code
  • The AOP implementation adds before and after methods to Function. Prototype, but many people don’t like this way of polluting the prototype, so we can make some changes and pass before and after methods as arguments:
	var before = function (fn, beforefn) {
      return function () {
        beforefn.apply(this.arguments)
        return fn.apply(this.arguments)}}var a = before(
      function () {
        alert(3)},function () {
        alert(2)
      }
    )
    a = before(a, function () {
      alert(1)
    })
    a()
Copy the code
5. Decorator mode and agent mode

The decorator pattern and the proxy pattern look very similar in structure. Both patterns describe how to provide some degree of indirect reference to an object, and their implementation parts retain a reference to another object and send requests to that object.

The most important difference between the agent pattern and the decorator pattern is their intent and design purpose. The purpose of the proxy pattern is to provide a substitute for an ontology when direct access to that ontology is inconvenient or undesirable. The ontology defines key functionality, and the proxy provides or denies access to it, or does additional things before accessing the ontology. The purpose of decorator pattern is to dynamically add behavior to an object. In other words, the Proxy pattern emphasizes a relationship (between a Proxy and its entities) that can be expressed statically, that is, it can be determined at the outset. The decorator pattern is used when the full functionality of the object is initially uncertain. The proxy pattern typically has only one layer of references to the proxy  ontology, whereas the decorator pattern often forms a long chain of decorators.