Декоратор - паттерн программирования, который позволяет взять существующую функцию и изменить/расширить ее поведение.
Декоратор получает функцию и возвращает обертку, которая модифицирует (декорирует) её поведение, оставляя синтаксис вызова тем же.
Пример декоратора
Например, у нас есть функция 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); // ...
Зачем декораторы?
Декораторы меняют поведение функции прозрачным образом.
-
Декораторы можно повторно использовать. Например,
doublingDecoratorможно применить не только кsum, но и кmultiply,divide. Декоратор для проверки прав можно применить к любой функции. - Несколько декораторов можно скомбинировать. Это придает дополнительную гибкость коду.
Примеры использования есть в задачах.
Задачи
Создайте декоратор 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, другой аргумент => другое значение
Комментарии
- Приветствуются комментарии, содержащие дополнения и вопросы по статье, и ответы на них.
- Если ваш комментарий касается задачи -- откройте её в отдельном окне и напишите там.
- Комментарии без смысла, с рекламой или не о статье вообще - удаляются.