Как использовать декораторы с фабричными функциями в JavaScript
Декораторы и фабричные функции — полезные инструменты JavaScript разработчика. Рассказываем, как использовать их вместе.
9К открытий9К показов
Декораторы методов дополняют объектно-ориентированное программирование, инкапсулируя функциональность, которую используют разные объекты.
Рассмотрим следующий код:
Задача метода add()
— добавлять новые элементы во внутреннее состояние. Кроме того, метод должен проверить авторизацию пользователя и записать продолжительность выполнения. Эти две задачи второстепенны и могут повторяться в других методах.
Представим, что мы можем поместить решение этих задач в отдельные функции. Например, так:
Теперь метод add()
просто добавляет новый элемент в список. Всё остальное решается декорированием метода.
logDuration()
и authorize()
— декораторы.
Декоратор функции — это функция высшего порядка, которая принимает в качестве аргумента одну функцию и возвращает другую, которая является вариацией функции-аргумента.Реджинальд Брейтуэйт, «JavaScript Allongé»
Логирование продолжительности
Декораторы можно использовать, чтобы узнать, сколько выполнялся метод:
Обратите внимание, что декорируемая функция выполняется через передачу текущего значения this
и всех аргументов: fn.apply(this, args)
.
Авторизация
Декоратор authorize()
проверяет, есть ли у пользователя необходимые права для выполнения метода. Он сложнее предыдущего, так как зависит от другого объекта currentUser
. В этом случае мы можем использовать функцию createAuthorizeDecorator()
для создания декоратора. Он выполнит метод, только если пользователь авторизован:
Теперь можно создать декоратор и передать зависимости:
let authorize = createAuthorizeDecorator(currentUser);
compose()
Часто требуется применить несколько декораторов к одному методу. Простой способ сделать это — вызывать декораторы один за другим:
Другой способ — собрать все декораторы в один и применить новый декоратор к исходной функции. Можно использовать функцию compose()
из библиотек вроде underscore.js.
При композиции функций одна функция применяется к результату другой.
Применение f()
к результату g()
означает compose(f, g)(x)
, что аналогично f(g(x))
.
Композиция лучше всего работает с унарными функциями вроде наших декораторов. Унарная функция — это функция, принимающая один аргумент:
Ниже показано, как можно использовать compose()
для применения декораторов на методе add()
:
Порядок
В некоторых случаях порядок применения декораторов имеет значение.
В нашем примере они применяются слева направо:
- Начинается логирование продолжительности вызова.
- Выполняется авторизация.
- Вызывается исходный метод.
Фабричные функции
Фабричные функции лучше классов по следующим причинам:
- Инкапсуляция. Члены приватны по умолчанию, поэтому можно самостоятельно решить, какие методы предоставить публичному API. В классах все члены публичны.
- Нет проблем с потерей контекста
this
. Фабричные функции не используютthis
, поэтому отсутствуют связанные с ним проблемы. - Безопасность объектов лучше. Внутреннее состояние инкапсулируется, и API неизменно. А, например, глобальный объект, объявленный с помощью
class
, можно изменить в консоли разработчика.
Уберём самовыполняющуюся часть из паттерна Revealing Module и получим фабричную функцию. Ниже показано определение фабричной функции TodoStore()
:
Декорирование фабричной функции
Зачастую требуется применить декораторы на всех публичных методах объекта. Для этого можно создать функцию decorateMethods()
:
Теперь можно использовать функцию decorateAndFreeze()
для декорирования всех публичных методов фабричной функции:
Декораторы работают как ключевые слова или аннотации — документируют поведение метода, но явно отделяют второстепенную логику от основной логики метода.Реджинальд Брейтуэйт, «JavaScript Allongé»
Сохранение контекста метода
Если используется не фабричная функция, декоратор всё равно должен сохранять контекст исходного метода, чтобы не потерять значение this
. Для этого нужно следовать двум правилам:
- Декоратор должен возвращать функцию с помощью ключевого слова
function
(никаких стрелочных функций). - Исходная функция должна вызываться с помощью
call(this, ...)
илиapply(this, ...)
.
Если им не следовать, то декоратор потеряет контекст this
, однако фабричные функции будут работать, так как они не используют this
.
Классы, функции-конструкторы и литералы объектов используют this
, поэтому они потеряют контекст.
Взглянем на следующий пример:
Вывод
Фабричные функции и декораторы — мощные инструменты в арсенале разработчика. Фабричные функции создают ООП-объекты, а декораторы инкапсулируют общую логику, которую эти объекты могут повторно использовать. Эти две концепции дополняют друг друга.
Кроме того, декораторы улучшают читаемость. Код становится гораздо чище, так как он фокусируется на своей основной задаче и не отвлекается на второстепенную логику.
9К открытий9К показов