Reception programming "Decorator"

Lecture



"

  1. Decorator example
  2. Another example
  3. Why decorators?
  4. Tasks

Decorator is a programming technique that allows you to take an existing function and change / expand its behavior.

The decorator gets the function and returns a wrapper that modifies ( decorates ) its behavior, leaving the call syntax the same.

Decorator example

For example, we have a function sum(a,b) :

function sum(a, b) {
   return a + b;
}

Create a doublingDecorator decorator, which changes the behavior, increasing the result of the function two times:

01 function doublingDecorator(f) {
02    return function () {
03      return 2*f.apply( this , arguments); // (*)
04    };
05 }
06
07 // Использование:
08
09 function sum(a, b) {
10    return a + b;
11 }
12
13 sum = doublingDecorator(sum);
14
15 alert( sum(1,2) ); // 6
16 alert( sum(2,3) ); // 10

The doublingDecorator decorator creates an anonymous wrapper function that, in line (*) calls f using apply with the same this context and arguments arguments , and then doubles the result.

This decorator can be applied twice:

1 sum = doublingDecorator(sum);
2 sum = doublingDecorator(sum);
3
4 alert( sum(1,2) ); // 12, т.е. 3 умножается на 4

The context of this in sum is not used at all, so one could call f.apply(null, arguments) .

Another example

Let's see another example. Suppose we have an isAdmin() function that returns true if the visitor has administrator rights.

You can create a universal decorator that adds rights verification to the function:

For example, create a decorator checkPermissionDecorator(f) . It will return a wrapper that sends the call to f if the visitor has enough rights:

1 function checkPermissionDecorator(f) {
2    return function () {
3      if ( isAdmin() ) {
4        return f.apply( this , arguments);
5      }
6      alert( 'Недостаточно прав' );
7    }
8 }
Use Decorator:
1 function save() { ... }
2
3 save = checkPermissionDecorator(save);
4 // Теперь вызов функции save() проверяет права

Decorators can be used in any combination:

sum = checkPermissionDecorator(sum);
sum = doublingDecorator(sum);
// ...

Why decorators?

Decorators change the behavior of a function in a transparent manner.

  1. Decorators can be reused. For example, doublingDecorator can be applied not only to sum , but also to multiply , divide . Decorator to check the rights can be applied to any function.
  2. Multiple decorators can be combined. This gives additional flexibility to the code.

Examples of use are in tasks.

Tasks

Importance: 5

Create the makeLogging(f, log) decorator, which takes the function f and the log array.

It must return a wrapper around f , which, with each call, writes (“logs”) the arguments to log , and then transfers the call to f .

In this problem, we can assume that the function f exactly one argument.

Should work like this:

01 function work(a) {
02    /* ... */ // work - произвольная функция, один аргумент
03 }
04
05 function makeLogging(f, log) { /* ваш код */ }
06
07 var log = [];
08 work = makeLogging(work, log);
09
10 work(1); // 1, добавлено в log
11 work(5); // 5, добавлено в log
12
13 for ( var i=0; i<log.length; i++) {
14    alert( 'Лог:' + log[i] ); // "Лог:1", затем "Лог:5"
15 }

Decision
[Open task in new window]

Importance: 3

Create the makeLogging(func, log) decorator makeLogging(func, log) , for the func function, returning a wrapper that, with each call, adds its arguments to the log array.

The condition is similar to the task Logging decorator (1 argument), but func allowed with any set of arguments.

Should work like this:

01 function work(a, b) {
02    alert(a + b); // work - произвольная функция
03 }
04
05 function makeLogging(f, log) { /* ваш код */ }
06
07 var log = [];
08 work = makeLogging(work, log);
09
10 work(1, 2); // 3
11 work(4, 5); // 9
12
13 for ( var i=0; i<log.length; i++) {
14    alert( 'Лог:' + [].join.call(log[i]) ); // "Лог:1,2", "Лог:4,5"
15 }

Decision

The solution is similar to the task Logging decorator (1 argument), the difference is that the entire arguments object goes to the log instead of a single arguments .

To transfer a call with an arbitrary number of arguments, use f.apply(this, arguments) .

01 function work(a, b) {
02    alert(a + b); // work - произвольная функция
03 }
04
05 function makeLogging(f, log) {
06  
07    function wrapper() {
08      log.push(arguments);
09      return f.apply( this , arguments);
10    }
11
12    return wrapper;
13 }
14
15 var log = [];
16 work = makeLogging(work, log);
17
18 work(1, 2); // 3
19 work(4, 5); // 9
20
21 for ( var i=0; i<log.length; i++) {
22    alert( 'Лог:' + [].join.call(log[i]) ); // "Лог:1,2", "Лог:4,5"
23 }

[Open task in new window]

Importance: 5

Create a makeCaching(f) decorator that takes the function f and returns a wrapper that caches its results.

In this problem, the function f has only one argument, and it is a number.

  1. The first time the wrapper is called with a specific argument, it calls f and remembers the value.
  2. For the second and subsequent calls with the same argument, the memorized value is returned.

It should work like this:

01 function f(arg) {
02    return Math.random()*arg; // может быть любой функцией
03 }
04
05 function makeCaching(f) { /* ваш код */ }
06
07 f = makeCaching(f);
08
09 var a, b;
10
11 a = f(1);
12 b = f(1);
13 alert( a == b ); // true (значение закешировано)
14
15 b = f(2);
16 alert( a == b ); // false, другой аргумент => другое значение

Decision

We will remember the results of the function call in the closure, in the cache: { ключ:значение } object cache: { ключ:значение } .

01 function f(x) {
02    return Math.random()*x;
03 }
04
05 function makeCaching(f) {
06    var cache = {};
07
08    return function (x) {
09      if (!(x in cache)) {
10        cache[x] = f.call( this , x);
11      }
12      return cache[x];
13    };
14
15 }
16
17 f = makeCaching(f);
18
19 var a = f(1);
20 var b = f(1);
21 alert( a == b ); // true (значение закешировано)
22
23 b = f(2);
24 alert( a == b ); // false, другой аргумент => другое значение

Note: checking for the presence of the already calculated value looks like this: if (x in cache) . Less universally, you can check this: if (cache[x]) , this is if we know for sure that cache[x] will never be false , 0 , etc.


Comments


To leave a comment
If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Scripting client side JavaScript, jqvery, BackBone

Terms: Scripting client side JavaScript, jqvery, BackBone