[Перевод] Паттерны проектирования в современной JavaScript-разработке

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

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

image

Цель этой статьи заключается в том, чтобы заинтересовать читателей чем-то вроде формального представления знаний в сфере разработки программного обеспечения, продемонстрировав им идею паттернов проектирования и описав несколько паттернов, которые интересны тем, что они нашли применение в современной JavaScript-Разработке.

Паттерн «синглтон»


▍Общие сведения


Паттерн проектирования «синглтон» (singleton), который ещё называют «одиночка», нельзя назвать одним из наиболее широко используемых паттернов, но мы начинаем разговор именно с него, так как его сравнительно легко понять.

Этот паттерн произрастает из математической концепции синглтона — одноэлементного множества, то есть множества, содержащего лишь один элемент. Например, множество {null} — это синглтон.

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

50d2e18c5888907acb07e86cab34fb27.png


Зачем ещё один супергерой, когда у нас есть Бэтмэн?

Выше показан пример класса, реализующего паттерн «синглтон».

▍Зачем он нужен?


Понятно, что этот паттерн позволяет нам ограничиться лишь одним «супергероем» (это, очевидно, Бэтмэн). Но зачем ещё он может понадобиться?

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

▍Где его используют?


В качестве основного примера использования паттерна «синглтон» в крупных популярных фреймворках можно назвать сервисы Angular. В документации по Angular есть отдельная страница, посвящённая разъяснению того, как сделать сервис синглтоном.

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

Рассмотрим пример. Предположим, у нас имеется простейшее приложение, которое используется для подсчёта количества нажатий на кнопки.

5138923bf446b4a737cca97a98d75e92.png


Каждый щелчок по любой из кнопок обновляет счётчик

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

  • Он позволяет подсчитывать щелчки по кнопкам.
  • Он даёт возможность считывать текущее значение счётчика щелчков.


Если подобный объект не был бы синглтоном (и с каждой кнопкой был бы связан собственный экземпляр такого объекта), то программа неправильно подсчитывала бы число нажатий на кнопки. Кроме того, при таком подходе нужно решить следующую задачу: «Из какого именно объекта, отвечающего за подсчёт щелчков, будут браться данные, выводимые на экран?».

Паттерн «наблюдатель»


▍Общие сведения


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

Понять этот паттерн совсем несложно в том случае, если найти его аналогию в реальном мире. А именно, речь идёт о подписках на газеты.

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

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

82016ce5faf7c7b5526b8b7767590501.jpg


Наконец можно почитать газету

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

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

4b46c8e4eea800ddf38d68f42676d795.png


Теперь вы никогда не пропустите любимую утреннюю газету

У рассматриваемого паттерна есть одна приятная особенность: вы не обязаны быть единственным наблюдателем. Если вы не сможете достать свою любимую газету — вас это расстроит. Но то же самое произойдёт и с другими людьми, которые не смогут её купить. Именно поэтому на одного субъекта могут подписываться несколько наблюдателей.

▍Зачем он нужен?


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

JavaScript-приложения — это отличное место для применения паттерна «наблюдатель», так как здесь всё управляется событиями, и, вместо того, чтобы постоянно обращаться к некоей сущности, узнавая, произошло ли интересующее вас событие, гораздо лучше будет дать ей возможность оповестить вас при возникновении этого события (это похоже на старое высказывание: «Не надо нам звонить. Когда надо, мы сами вам позвоним»).

Вполне вероятно, что вы уже пользовались конструкциями, напоминающими паттерн «наблюдатель». Например — это addEventListener. Добавление к элементу прослушивателя событий имеет все признаки использования паттерна «наблюдатель»:

  • Вы можете подписаться на объект.
  • Вы можете отписаться от объекта.
  • Объект может информировать о событии всех своих подписчиков.


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

▍Где его используют?


Базовая реализация этого паттерна не должна быть особенно сложной, но существуют отличные библиотеки, реализующие его и используемые во многих проектах. Речь идёт о проекте ReactiveX, и о его JavaScript-варианте RxJS.

Библиотека RxJS позволяет не только подписываться на субъекты, но и даёт программисту возможность трансформации данных самыми разными способами, позволяет комбинировать множество подписок, улучшает возможности по управлению асинхронными операциями. Кроме того, этим её возможности не ограничиваются. Если вы когда-нибудь хотели поднять возможности своих программ по обработке и преобразованию данных на более высокий уровень, то вам можно порекомендовать изучить библиотеку RxJS.

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

Паттерн «фасад»


▍Общие сведения


Паттерн «фасад» (facade) получил своё название из архитектуры. В архитектуре фасад — это, обычно, одна из внешних сторон здания, как правило — передняя сторона. Английский язык позаимствовал слово «facade» из французского. Речь идёт о слове «façade», которое, кроме прочего, переводится как «лицевая сторона здания».

Фасад здания в архитектуре — это внешняя часть здания, скрывающая то, что находится внутри. Похожие свойства можно отметить и у паттерна «фасад», так как он направлен на то, чтобы скрыть сложные внутренние механизмы за неким внешним интерфейсом. Его применение позволяет разработчику работать с внешним API, устроенным достаточно просто, и, в то же время, предоставляет возможность менять внутренние механизмы, скрытые за фасадом, не нарушая работоспособность системы.

▍Зачем он нужен?


Паттерн «фасад» можно использовать в огромном количестве ситуаций, среди которых можно особо отметить те из них, когда код пытаются сделать более простым для понимания (то есть — скрывают сложные механизмы за простыми API), и те, когда фрагменты систем стремятся сделать как можно слабее связанными друг с другом.

d3e7e51fbd6d7f239e8629609aa2f661.png


Этим объектам нужно что-то из логова дракона

Несложно заметить, что объект-фасад (или слой с несколькими объектами) — это весьма полезная абстракция. Вряд ли кто-нибудь захочет столкнуться с драконом в том случае, если этого можно избежать. Объект-фасад нужен для того, чтобы предоставить другим объектам удобный API, а со всеми драконьими хитростями этот объект будет справляться самостоятельно.

Ещё одна полезная особенность паттерна «фасад» заключается в том, что дракона можно как угодно «переделывать», но это никак не скажется на других частях приложения. Предположим, вы хотите заменить дракона на котёнка. У котёнка, как и у дракона, есть когти, но его легче кормить. Поменять дракона на котёнка — это значит — переписать код объекта-фасада без внесения изменений в зависимые объекты.

▍Где его используют?


Паттерн «фасад» часто можно встретить в Angular. Там сервисы используются как средства для упрощения некоей базовой логики. Но этот паттерн применим не только в Angular, ниже вы сможете в этом убедиться.

Предположим, нам нужно добавить в приложение систему управления состоянием. Для решения этой задачи можно воспользоваться разными средствами, среди них — Redux, NgRx, Akita, MobX, Apollo, а также — постоянно появляющиеся новые инструменты. Почему бы не испытать их все?
Какова основная функциональность, которую должна предоставлять библиотека для управления состоянием? Вероятно, это — следующие возможности:

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


Выглядит всё это не так уж и плохо.

Теперь, вооружившись паттерном «фасад», можно написать фасады для работы с различными частями состояния, предоставляющие удобные API, которыми можно пользоваться в программе. Например, нечто вроде facade.startSpinner(), facade.stopSpinner() и facade.getSpinnerState(). Подобные методы просто понять, на них легко можно сослаться в разговоре о программе.

После этого вы можете поработать с объектами, реализующими паттерн «фасад» и написать код, который будет трансформировать ваш код так, чтобы он мог бы работать с Apollo (управление состоянием с помощью GraphQL — горячая тема). Возможно, в ходе испытаний вы обнаружите, что Apollo вам не подходит, или то, что вам неудобно писать модульные тесты в расчёте на эту систему управления состоянием. Нет проблем — напишите новый фасад, рассчитанный на поддержку MobX, и снова испытайте систему.

d17329e50a91025847eaeb93ad94b741.png


Разные системы управления состоянием приложения, доступ к которым осуществляется через единый фасад, тоже могут быть драконами…

Итоги


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

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

Уважаемые читатели! Какими паттернами проектирования вы пользуетесь?

1ba550d25e8846ce8805de564da6aa63.png

© Habrahabr.ru