Практический пример разделения фабричного шаблона

Проблема

Недавно я разработал расширение для Chrome под названием Retro Notes, используя React. Содержимое, которое вы заполняете в текстовых полях, синхронизируется с вашим профилем Chrome с помощью API синхронизации хранилища Chrome. Реализация довольно проста — я напрямую использую API, как описано в документации, для получения и установки значений хранилища.

Расширение работает должным образом при запуске в контексте расширения Chrome. Таким образом, приложение получает доступ к API хранилища Chrome. Однако, когда я запускаю это приложение React как отдельный веб-сайт, оно дает сбой, потому что больше не может получить доступ к API хранилища Chrome.

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

Итак, как нам это решить?

Решение

Проблема здесь в том, что у нас есть тесная связь между двумя компонентами — пользовательским компонентом React и объектом API хранилища Chrome. При работе без контекста расширения Chrome chrome.storage.sync становится недействительным, и из-за этой тесной связи мы также не можем легко заменить это. Тесная связь обычно затрудняет тестирование, и из-за прямой зависимости изменения зависимости также влияют на зависимый компонент. Поэтому это считается плохой практикой.

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

Шаг 1. Разделение деталей реализации хранилища

Начнем с извлечения кода API хранилища Chrome в отдельный файл. Мы обернем метод chrome.storage.sync.set() в общий метод setStorage() и просто передадим соответствующие параметры. Я использую TypeScript, поэтому я могу создать интерфейс для строгой типизации этих методов и использовать этот интерфейс здесь. Обратите внимание, что я назвал интерфейс IStorageApi вместо IChromeStorageApi — для этого есть причина, и мы вернемся к ней позже в этой статье. Полный фрагмент кода можно найти ниже.

Теперь мы можем импортировать созданный выше файл API хранилища Chrome в наш пользовательский компонент, а затем вместо этого вызвать метод setStorage().

Это лучше — мы больше не связываем жестко функциональность хранилища с пользовательским компонентом, но по-прежнему тесно связываем метод хранения. Допустим, я решаю поменять метод хранения и вместо использования API хранилища Chrome использую конечную точку API моего собственного внутреннего сервера. Это означало бы, что мне нужно было бы реорганизовать этот файл только для того, чтобы, например, обновить ссылки с ChromeStorageApi на CustomStorageApi.

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

Шаг 2. Разделение метода хранения

Чтобы решить эту проблему, давайте создадим еще один файл с именем DevelopmentStorage.ts, который наследует тот же интерфейс IStorageApi, но при реализации метода вместо использования API хранилища Chrome мы будем хранить данные в локальном объекте. Поскольку мы должны иметь возможность реализовывать различные реализации интерфейса хранилища, более разумно назвать интерфейс чем-то общим, например IStorageApi. Полный фрагмент кода приведен ниже.

Далее мы создадим фабрику. В зависимости от условия он либо возвращает фактический модуль API хранилища Chrome, либо возвращает смоделированный модуль API хранилища разработки. В нашем случае, когда мы запускаем сайт как расширение, process.env.NODE_ENV устанавливается на production, а когда мы запускаем сайт как отдельный сайт, значение устанавливается на development.

Наконец, давайте обновим пользовательский компонент и будем использовать сгенерированный на заводе модуль вместо прямой ссылки на конкретный модуль.

Компоненту React не нужно знать детали реализации того, как элементы извлекаются и сохраняются в хранилище Chrome. Он может просто взаимодействовать с API, который отвечает за предоставление этой функции хранилища. Таким образом, если мы хотим сохранить значения в нашем пользовательском хранилище, нам не нужно будет вносить какие-либо изменения в пользовательский компонент.

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

Рекомендации

Если вы хотите увидеть полный код Retro Notes, он доступен на GitHub. Если вы хотите попробовать расширение Retro Notes для Chrome, загрузите его из Интернет-магазина Chrome.

Вот и все. Спасибо за прочтение!