Мастер-классы по Javascript Екатеринбург Ростов-на-Дону Москва Узнать больше...
Содержание (скрыть) Содержание (показать)

Декораторы

  1. Пример декоратора
  2. Ещё пример
  3. Зачем декораторы?
  4. Задачи

Декоратор - паттерн программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.

Декоратор получает функцию и возвращает обертку, которая модифицирует (декорирует) её поведение, оставляя синтаксис вызова тем же.

Пример декоратора

Например, у нас есть функция sum(a,b):

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

Создадим декоратор doublingDecorator, который меняет поведение, увеличивая результат работы функции в два раза:

function doublingDecorator(f) {         
  return function() {
    return 2*f.apply(this, arguments); // (*)
  };
}

// Использование:

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

sum = doublingDecorator(sum); 

alert( sum(1,2) ); // 6
alert( sum(2,3) ); // 10

Декоратор doublingDecorator создает анонимную функцию-обертку, которая в строке (*) вызывает f при помощи apply с тем же контекстом this и аргументами arguments, а затем удваивает результат.

Этот декоратор можно применить два раза:

sum = doublingDecorator(sum); 
sum = doublingDecorator(sum); 

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

Контекст this в sum никак не используется, поэтому можно бы было вызвать f.apply(null, arguments).

Ещё пример

Посмотрим еще пример. Предположим, у нас есть функция isAdmin(), которая возвращает true, если у посетителя есть права администратора.

Можно создать универсальный декоратор, который добавляет в функцию проверку прав:

Например, создадим декоратор checkPermissionDecorator(f). Он будет возвращать обертку, которая передает вызов f в том случае, если у посетителя достаточно прав:

function checkAdminDecorator(f) {
  return function() {
    if ( isAdmin() ) {
      return f.apply(this, arguments); 
    }
    alert('Недостаточно прав');
  }
}
Использование декоратора:
function save() { ... }

save = checkPermissionDecorator(save);
// Теперь вызов функции save() проверяет права

Декораторы можно использовать в любых комбинациях:

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

Зачем декораторы?

Декораторы меняют поведение функции прозрачным образом.

  1. Декораторы можно повторно использовать. Например, doublingDecorator можно применить не только к sum, но и к multiply, divide. Декоратор для проверки прав можно применить к любой функции.
  2. Несколько декораторов можно скомбинировать. Это придает дополнительную гибкость коду.

Примеры использования есть в задачах.

Задачи

Создайте декоратор makeLogging(f, log), которая берет функцию одного аргумента f и массив log. Она возвращает обертку, которая при каждом вызове записывает («логирует») аргументы в log.

У функции f может быть только один аргумент.

Работать должно так:

function work(a) { 
  /* ... */ // work - произвольная функция, один аргумент
}

function makeLogging(f, log) { /* ваш код */ }

var log = [];
work = makeLogging(work, log);

work(1); // 1, добавлено в log
work(5); // 5, добавлено в log

for(var i=0; i<log.length; i++) {
  alert( 'Лог:' + log[i] ); // "Лог:1", затем "Лог:5"
}

Решение
Решение

Возвратим декоратор wrapper который будет записывать аргумент в log и передавать вызов в f:

function work(a) { 
  /*...*/ // work - произвольная функция, один аргумент
}

function makeLogging(f, log) {

*!*
  function wrapper(a) {
    log.push(a);
    return f.call(this, a);   
  }
*/!*

  return wrapper;
}

var log = [];
work = makeLogging(work, log);

work(1); // 1
work(5); // 5

for(var i=0; i<log.length; i++) {
  alert( 'Лог:' + log[i] ); // "Лог:1", затем "Лог:5"
}

При вызове f.call на всякий случай передадем и this, ведь функция может быть вызвана и в контексте объекта.

Создайте декоратор makeLogging(func, log), которая берет функцию func и возвращает обертку, которая при каждом вызове добавляет аргументы arguments в массив log.

Условие аналогично задаче Логирующий декоратор (1 аргумент), но допускается func с любым набором аргументов.

Работать должно так:

function work(a, b) { 
  alert(a + b); // work - произвольная функция
}

function makeLogging(f, log) { /* ваш код */ }

var log = [];
work = makeLogging(work, log);

work(1, 2); // 3
work(4, 5); // 9

for(var i=0; i<log.length; i++) {
  alert( 'Лог:' + [].join.call(log[i]) ); // "Лог:1,2", "Лог:4,5"
}

Решение
Решение

Решение аналогично задаче Логирующий декоратор (1 аргумент), разница в том, что в в лог вместо одного аргумента идет весь объект arguments.

Для передачи вызова с произвольным количеством аргументов используем f.apply(this, arguments).

function work(a, b) { 
  alert(a + b); // work - произвольная функция
}

function makeLogging(f, log) {
 
*!*
  function wrapper() {
    log.push(arguments);
    return f.apply(this, arguments);   
  }
*/!*

  return wrapper;
}

var log = [];
work = makeLogging(work, log);

work(1, 2); // 3
work(4, 5); // 9

for(var i=0; i<log.length; i++) {
  alert( 'Лог:' + [].join.call(log[i]) ); // "Лог:1,2", "Лог:4,5"
}

Создайте декоратор makeCaching(f), который берет функцию f(arg) и возвращает обертку, которая кеширует её результаты.

То есть, при первом вызове обертки с определенным аргументом она вызывает f и запоминает значение.

При втором и последующих вызовах с тем же аргументом возвращается запомненное значение.

Для простоты - пусть функция f имеет только один аргумент, и он является числом.

Должно работать так:

function f(arg) { 
  return Math.random()*arg; // любая функция одного аргумента
}

function makeCaching(f) { /* ваш код */ }

f = makeCaching(f);

var a = f(1);
var b = f(1);
alert( a == b ); // true (значение закешировано)

b = f(2);
alert( a == b ); // false, другой аргумент => другое значение

Решение
Решение

Запоминать результаты вызова функции будем в замыкании, в объекте cache: { ключ:значение }.

function f(arg) { 
  return Math.random()*arg;
}

*!*
function makeCaching(f) { 
  var cache = {};  

  function wrapper(arg) {
    if (!(arg in cache)) {   
      cache[arg] = f.call(this, arg);
    }
    return cache[arg];
  }

  return wrapper;
}
*/!*

f = makeCaching(f);

var a = f(1);
var b = f(1);
alert( a == b ); // true (значение закешировано)

b = f(2);
alert( a == b ); // false, другой аргумент => другое значение


Комментарии

  1. Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
  2. Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
  3. Комментарии без смысла, с рекламой или не о статье вообще - удаляются.
Наверх

Содержание

Реклама

Нашли опечатку?

Нашли опечатку на сайте? Что-то кажется странным?
Выделите соответствующий текст и нажмите Ctrl+Enter!

Последние Комментарии

Помоги другим!

Помоги другим узнать о хорошей статье!