Demethodizing & Methodizing: от методов к функциям и обратно

81830ef5412b2ee6ec14072b4d2e5dea

Во время работы с кодом проекта периодически могут возникать ситуации, когда для улучшения структуры кода и его читаемости необходимо использовать метод объекта вне его контекста. Например, есть метод объекта, который подходит для решения задачи, но его привязка к конкретному объекту ограничивает возможности его применения. Или, наоборот, может понадобиться взять обычную функцию и превратить ее в метод объекта.

Функциональное программирование предлагает элегантные решения в виде двух техник: деметодизации (demethodizing) и методизации (methodizing).

В статье рассмотрим обе техники и на простых примерах, покажем, как demethodizing и methodizing могут расширить возможности работы с кодом. Для демонстрации этих подходов будем использовать TypeScript.

Demethodizing

Demethodizing — это техника функционального программирования, позволяющая извлекать метод из объекта и преобразовывать его в независимую функцию.

Мы можем реализовать функцию demethodize () тремя равнозначными способами: используя методы apply (), call (), или bind ().

Реализация с применением метода apply ():

const demethodize =
 any>(fn: T) =>
(obj: any, ...args: Parameters): ReturnType =>
fn.apply(obj, args);

Реализация с применением метода call ():

const demethodize =
 any>(fn: T) =>
(obj: any, ...args: Parameters): ReturnType =>
fn.call(obj, ...args);

Функция demethodize () принимает объект в качестве первого аргумента, остальные аргументы (…args) — это фактические параметры, которые передаются вызываемому методу.

Реализация с применением метода bind ():

const demethodize =
 any>(fn: T) =>
(thisArg: any, ...args: Parameters): ReturnType =>
fn.bind(thisArg, ...args)();

Функция demethodize (), реализованная с помощью bind (), принимает в качестве первого аргумента функцию fn, которая ожидает контекст this и аргументы.

Рассмотрим, как demethodize () может быть использован на практике.

Предположим, что есть коллекция элементов, например, NodeList, которую получили с помощью document.querySelectorAll. В современных браузерах мы могли бы преобразовать этот объект в массив с помощью Array.from () и затем применить необходимый нам метод массива, например метод map (). Однако, если нужно поддерживать старые браузеры, которые не поддерживают Array.from (), этот подход становится неприменимым.

Вместо этого можно использовать demethodize (), чтобы преобразовать метод массива в независимую функцию, которую затем можно применить к NodeList. Это обеспечит возможность использования метода массива, минуя необходимость преобразования NodeList в массив.

const map = demethodize(Array.prototype.map); 
// Преобразуем метод Array.prototype.map в независимую функцию

const paragraphs = document.querySelectorAll(‘p');
// Получаем все параграфы 
                                          
const paragraphTexts = map(paragraphs, p => p.textContent); 
// Извлекаем текст из каждого параграфа 

В приведенном выше примере функция demethodize () позволяет преобразовать метод Array.prototype.map и применять его к NodeList напрямую. Т.о., мы можем эффективно обрабатывать коллекции элементов и извлекать из них информацию, даже если встроенные методы преобразования коллекций недоступны.

Methodizing

В предыдущем разделе статьи мы узнали, как извлечь метод из объекта и преобразовать его в независимую функцию. В этом разделе будет рассмотрен обратный процесс.

Methodizing — это техника функционального программирования, которая позволяет добавлять функцию в объект в качестве метода, обеспечивая доступ функции к контексту объекта.

Напишем реализацию функции methodize ():

const methodize = <
  T extends any[],
  O extends { prototype: { [key: string]: any } },
  F extends (arg: any, ...args: T) => any
>(obj: O, fn: F) =>
  (obj.prototype[fn.name] = function (
    this: Parameters[0],
    ...args: T
  ): ReturnType {
    return fn(this, ...args);
  });

T — обобщённый тип параметров, которые будут передаваться в методизированную функцию.
O — тип объекта, в прототип которого добавляется новый метод.
F — функция, которую мы методизируем. Первый аргумент (arg) будет являться контекстом вызова (this). Остальные аргументы (если таковые имеются) имеют тип T.

Рассмотрим использование функции methodize () на примере добавления функции reverse () к объекту String.

function reverse(this: string): string {
  return this.split('').reverse().join('');
} 
// Функция reverse принимает строку в качестве аргумента 
// и возвращает новую строку с символами в обратном порядке


methodize(String, reverse); 
// Добавление метода к объекту String


'METODIZING'.reverse(); // ‘GNIZIDOTEM’

О преимуществах и недостатках

Demethodizing и methodizing — это мощные техники, которые при грамотном использовании способны улучшить читабельность и структуру кода. Эффективность этих техник зависит от контекста и соответствия задачам проекта. Рассмотрим, в каких ситуациях эти подходы приносят пользу, а когда их применение может оказаться неуместным.

Преимущества

Demethodizing превращает методы объектов в независимые функции, что позволяет использовать их в различных контекстах. Например, деметодизация метода Array.prototype.map открывает возможность применять его к любым итерируемым структурам данных, включая NodeList, тем самым увеличивая функциональность кода.

Также применение техники деметодизации устраняет зависимость от контекста, что делает код более понятным и предсказуемым. Уменьшение количества кода, зависящего от внешнего окружения, приведет к снижению вероятности возникновения ошибок, связанных с неправильным использованием контекста.

Methodizing позволяет превращать обычные функции в методы объектов, что делает код более гибким и модульным. Это особенно полезно, когда возникает необходимость создать методы для объектов, которые легко интегрируются в объектно-ориентированный код разрабатываемого приложения.

Применение техники методизации позволяет эффективно организовать код, интегрируя функции в объекты и тем самым упрощая их повторное использование в объектно-ориентированном стиле. Это может облегчить рефакторинг и сопровождение кода, особенно в крупных проектах, где важна модульность и организованность.

Т.о., применение описанных в статье техник способствует тому, что код становится более декларативным, упрощая его восприятие и делает его интуитивно понятным, особенно для тех, кто уже знаком с функциональным программированием.

Недостатки

В высоконагруженных системах, где важна каждая миллисекунда, чрезмерное использование функции demethodize () может привести к потере производительности, т.к. для реализации этой функции применяется один из трех методов: call (), apply () или bind (), которые требуют дополнительных вычислительных ресурсов для изменения контекста выполнения функции.

При использовании методов call () и apply (), функция вызывается с явно указанным контекстом и аргументами. Это требует дополнительных вычислений для установки нового контекста выполнения и обработки передаваемых аргументов.

При использовании bind () создается новая функция, которая сохраняет переданный контекст и аргументы. Создание новой функции требует выделения памяти и увеличивает время выполнения.

Упомянутые операции требуют времени и загружают процессор клиентского устройства. Дополнительные вычислительные затраты могут замедлить производительность приложения.

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

Заключение

В статье были рассмотрены две техники функционального программирования: деметодизация (demethodizing) и методизация (methodizing), а также их преимущества и недостатки.

Demethodizing позволяет преобразовать метод объекта в независимую функцию. Были рассмотрены три способа реализации функции demethodize () с помощью методов apply (), call () и bind (). Пример использования demethodize () показал, как можно эффективно работать с коллекциями элементов, такими как NodeList, минуя необходимость их преобразования в массив.

Methodizing позволяет интегрировать функцию в объект в качестве метода, что дает функции доступ к внутреннему состоянию объекта. В качестве иллюстративного примера была использована функция methodize (), которая добавляет метод reverse () к прототипу объекта String.

Demethodizing и Methodizing предоставляют дополнительные возможности для работы с функциями и объектами.

© Habrahabr.ru