Прокси не должны все время звучать скучно.

Один из наиболее интересных паттернов, который я изучил на более позднем этапе своей карьеры, — это прокси.

Когда вы ищете примеры шаблона Proxy, вы часто можете увидеть различные варианты реализации. Это потому, что прокси не ограничен одним вариантом использования. Один прокси может выступать в роли валидатора, а другой может быть больше заинтересован в повышении производительности и т.д.

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

Другими словами, прокси находится прямо между клиентом объекта и самим объектом. Здесь он может действовать как «защитник» или добавлять пользовательскую логику, такую ​​как кеширование, при этом вызывающая сторона даже не узнает об этом. Из-за этого его иногда можно назвать Посредником. Некоторые могут также классифицировать его как еще одну форму шаблона Decorator, но есть некоторые отличия.

В этом посте мы рассмотрим возможности шаблона проектирования прокси в JavaScript и несколько примеров того, насколько полезным он может стать для вашего следующего приложения.

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

Разница между декоратором и прокси

В шаблоне декоратора основная обязанность декоратора заключается в улучшении объекта, который он обертывает (или «украшает»), тогда как прокси имеет больше возможностей доступа и контролирует объект.

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

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

Валидатор/Помощник

Первая реализация шаблона прокси, которую я покажу здесь, будет валидатором.

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

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

Прокси становится владельцем getter и setter свойства id и выбирает, разрешать или отклонять значения, которые пытаются установить.

В классе Proxy это можно реализовать примерно так:

Валидатор работает отлично:

Буфер обмена

В этом разделе мы рассмотрим использование прокси как способа поддержки старых браузеров при копировании выделенного текста в буфер обмена пользователя, гарантируя, что браузер поддерживает Navigator.clipboard API. Если это не так, то он вернется к использованию execCommand для копирования выделения.

Опять же, клиент всегда будет предполагать, что объект, для которого он вызывает методы, является исходным объектом, и знает только, что он вызывает указанный метод:

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

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

Кэширование (повышение производительности)

Кэширование может принимать множество различных форм в различных сценариях. Например, есть Stale While Revalidate для HTTP-запросов, кэширование контента nginx, кэширование CPU, кэширование ленивой загрузки, запоминание и т. д.

В JavaScript мы также можем добиться кэширования с помощью прокси.

Чтобы реализовать шаблон прокси без прямого использования класса Proxy, мы можем сделать что-то вроде этого:

Кэш:

Реализовать это непосредственно в классе Proxy очень просто:

Proxy класс

Мы видели постоянный шаблон в паре базовых реализаций прокси-шаблона по сравнению с прямым использованием класса Proxy. Поскольку JavaScript напрямую предоставляет Proxy в качестве объекта языка, оставшаяся часть этого поста будет использовать это для удобства.

Все остальные примеры можно выполнить без Proxy, но вместо этого мы сосредоточимся на синтаксисе класса, потому что он более лаконичен и с ним легче работать, особенно для этой статьи.

Прокси для Синглтона

Если вы никогда не слышали о Singleton, это еще один шаблон проектирования, который гарантирует, что интересующий объект будет возвращен и повторно использован, если он уже был создан на протяжении всего жизненного цикла приложения. На практике вы, скорее всего, увидите, что this используется как некоторая глобальная переменная.

Например, если мы пишем игру MMORPG и у нас есть три класса Equipment, Person и Warrior, из которых может существовать только один Warrior, мы можем использовать метод обработчика construct во втором аргументе, когда создание экземпляра Proxy в классе Warrior:

Если мы попытаемся создать несколько экземпляров Warrior, мы гарантируем, что каждый раз будет использоваться только первый созданный экземпляр:

Результат:

Похититель печенья

В этом разделе мы продемонстрируем пример использования Proxy для предотвращения мутаций из списка файлов cookie. Это предотвратит мутацию исходного объекта, и мутатор (CookieStealer) предположит, что его злонамеренная операция прошла успешно.

Давайте посмотрим на этот пример:

Еда Джорджа:

Мы создали экземпляр george с помощью класса Human и добавили в его хранилище 7 единиц еды. Джордж счастлив, что собирается съесть свои фрукты и печенье. Он особенно взволнован своим печеньем, потому что он получил все свои любимые вкусы одновременно, и вскоре будет поглощать их, чтобы удовлетворить свою тягу к печенью.

Однако есть проблема:

CookieStealer появляется ни с того ни с сего, чтобы украсть его печенье. Теперь у CookieStealer в хранилище есть 5 файлов cookie:

Джордж:

Если бы мы перемотали назад и представили, что наш спаситель Superman применяет один из его методов, реализующих шаблон Proxy, чтобы предотвратить CookieStealer от его злых действий, это решило бы нашу проблему:

К счастью, у нашего друга superman есть protectFromCookieStealers, использующий силу Proxy для подделки списка файлов cookie! Он хранит настоящую коллекцию продуктов, содержащих печенье Джорджа, подальше от CookieStealer. CookieStealer продолжает свои коварные планы и, по-видимому, обманом думает, что ему сошло с рук печенье:

CookieStealer уходит с печеньем на складе и думает, что ему это сошло с рук:

Он и не подозревает, что его обманул супермен и что это были поддельные печенья! Печенье george все еще нетронуто благодаря силе Proxy, спасающей его от тьмы зла:

Заключение

Я надеюсь, что это помогло пролить свет на шаблон Proxy и на то, как воспользоваться преимуществами этой концепции, используя теперь уже встроенный класс Proxy в JavaScript.

На этом мы заканчиваем этот пост :) Надеюсь, эта статья оказалась для вас полезной, и не забудьте подписаться на меня на Medium для будущих публикаций!

Дополнительные материалы на PlainEnglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter и LinkedIn. Присоединяйтесь к нашему сообществу Discord.