img

Полное руководство по шаблонам проектирования JavaScript

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

Преимущества шаблонов проектирования:

  • Решения проверены временем. Поскольку паттерны проектирования часто используются многими разработчиками, вы можете быть уверены, что они работают. Плюс они неоднократно пересматривались и, возможно, уже оптимизированы. 
  • Легко поддаются многократному использованию. Паттерны проектирования документируют многоразовое решение, которое может быть изменено для решения множества конкретных задач, поскольку они не привязаны к конкретной проблеме.
  • Облегчают коммуникацию. Когда разработчики знакомы с паттернами проектирования, им легче общаться друг с другом по поводу потенциальных решений той или иной проблемы.
  • Нет необходимости рефакторинга кода. Если приложение написано с учетом паттернов проектирования, часто бывает так, что в дальнейшем вам не придется рефакторить код, потому что применение правильного паттерна проектирования к конкретной задаче уже является оптимальным решением.
  • Уменьшают размер кодовой базы. Поскольку паттерны проектирования обычно представляют собой элегантные и оптимальные решения, они обычно требуют меньше кода, чем другие решения.

Краткая история JavaScript

JavaScript - один из самых популярных на сегодняшний день языков программирования для веб-разработки. Изначально он был создан как своего рода «клей» для различных отображаемых HTML-элементов, известный как язык сценариев на стороне клиента, для одного из первых веб-браузеров. Он назывался Netscape Navigator и в то время мог отображать только статический HTML. Как вы уже догадались, идея создания такого языка сценариев привела к войне за браузер между крупными игроками в индустрии разработки браузеров, такими как Netscape Communications (сегодня Mozilla), Microsoft и другими.

Каждый из крупных игроков хотел продвинуть свою собственную реализацию этого языка сценариев, поэтому Netscape создал JavaScript (на самом деле это сделал Брендан Эйх), Microsoft - JScript и так далее. Как вы понимаете, различия между этими реализациями были очень велики, поэтому разработка для веб-браузеров велась для каждого браузера, с использованием стикеров, которые прилагались к веб-странице. Вскоре стало ясно, что нам нужен стандарт, кроссбраузерное решение, которое унифицировало бы процесс разработки и упростило создание веб-страниц. То, что они придумали, называется ECMAScript.

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

Что такое JavaScript?

JavaScript - это легкий, интерпретируемый, объектно-ориентированный язык программирования с первоклассными функциями, наиболее известный как язык сценариев для веб-страниц.

Вышеупомянутое определение означает, что код JavaScript занимает мало памяти, прост в реализации и легко изучается, а его синтаксис схож с такими популярными языками, как C++ и Java. Это скриптовый язык, что означает, что его код интерпретируется, а не компилируется. Он поддерживает процедурный, объектно-ориентированный и функциональный стили программирования, что делает его очень гибким для разработчиков.

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

JavaScript поддерживает функции как объекты первого класса

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

JavaScript основан на прототипах

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

Только в ES6 был введен формальный термин "класс", что означает, что браузеры еще не полностью поддерживают его (если помните, на момент написания последняя полностью поддерживаемая версия ECMAScript — 5.1). Важно отметить, что, даже несмотря на то, что термин "класс" введен в JavaScript, под капотом он все равно использует прототипное наследование.

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

Циклы событий JavaScript

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

Каждый раз, когда происходит событие, к которому привязан обработчик (иначе событие теряется), сообщение отправляется в очередь сообщений, которые обрабатываются синхронно в порядке FIFO (первым пришел — первым обработан). Это называется циклом событий (event loop).

Каждое сообщение в очереди связано с определенной функцией. Когда сообщение извлекается из очереди, среда выполнения полностью выполняет функцию, прежде чем обработать следующее сообщение. Это означает, что если функция вызывает другие функции, они все будут выполнены до того, как будет обработано новое сообщение из очереди. Это называется выполнением до завершения (run-to-completion).

queue.waitForMessage() синхронно ожидает новые сообщения. Каждое обрабатываемое сообщение имеет свой собственный стек и обрабатывается до тех пор, пока стек не опустеет. Когда выполнение завершено, из очереди берется новое сообщение, если оно есть.

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

Что такое паттерн проектирования в JavaScript?

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

Прото-паттерн

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

Станет ли оно сразу же шаблоном? К счастью, нет. Часто бывает так, что человек имеет хорошую практику написания кода и просто принимает что-то, что выглядит как паттерн, за паттерн, когда на самом деле это не паттерн.

Как узнать, что то, что вы думаете, что узнали, на самом деле является шаблоном?

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

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

Антипаттерн

Как паттерн проектирования представляет собой хорошую практику, так и антипаттерн представляет собой плохую практику.

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

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

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

Категоризация паттернов проектирования

Шаблоны дизайна можно классифицировать множеством способов, но наиболее популярными являются следующие:

  • Творческие паттерны проектирования
  • Структурные паттерны проектирования
  • Поведенческие шаблоны проектирования
  • Конкурентоспособные шаблоны проектирования
  • Архитектурные шаблоны проектирования

Паттерны креативного проектирования

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

  • Метод фабрики
  • Абстрактная фабрика
  • Конструктор
  • Прототип
  • Синглтон

Структурные паттерны проектирования

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

  • Адаптер
  • Мост
  • Композит
  • Декоратор
  • Фасад
  • Flyweight
  • Прокси

Поведенческие паттерны проектирования

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

  • Цепочка ответственности
  • Команда
  • Итератор
  • Посредник
  • Memento
  • Наблюдатель
  • Состояние
  • Стратегия
  • Посетитель

Конкурентоспособные шаблоны проектирования

Эти типы паттернов проектирования связаны с парадигмами многопоточного программирования. Некоторые из них популярны:

  • Активный объект
  • Ядерная реакция
  • Планировщик

Архитектурные шаблоны проектирования

Шаблоны проектирования, которые используются в архитектурных целях. К наиболее известным из них относятся:

  • MVC (Model-View-Controller)
  • MVP (Model-View-Presenter)
  • MVVM (Model-View-ViewModel).

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

Примеры шаблонов проектирования

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

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

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

Шаблон конструктора 

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

Обычно в JavaScript объекты создаются следующими тремя способами:

После создания объекта существует четыре способа (начиная с ES3) добавить свойства в эти объекты. Вот они:

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

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

Однако здесь есть пространство для улучшений. Как я уже упоминал ранее, JavaScript использует наследование на основе прототипов. Проблема с предыдущим подходом заключается в том, что метод writesCode переопределяется для каждого экземпляра конструктора Person. Мы можем избежать этого, задав метод в прототип функции:

Теперь оба экземпляра конструктора Person могут получить доступ к общей версии метода writesCode().

Модульный паттерн

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

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

Как видите, с помощью IIFE (немедленно вызываемого функционального выражения) мы привязали переменную counter к функции, которая была вызвана и завершилась, но по-прежнему доступна дочерней функции, которая её увеличивает. Так как мы не можем получить доступ к переменной counter извне функционального выражения, мы сделали её приватной через манипуляцию с областью видимости.

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

Самое полезное, что вводит этот паттерн, — это чёткое разделение приватных и публичных частей объекта, что очень похоже на концепцию для разработчиков с классическим объектно-ориентированным опытом.

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

Раскрывающий модульный шаблон

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

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

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

Шаблон синглтона 

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

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

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

Паттерн посредника

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

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

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

Разница в том, что посредник управляет рабочим процессом, в то время как издатель/подписчик использует так называемый тип связи «отдать и забыть». Издатель/подписчик - это просто агрегатор событий, то есть он просто заботится о том, чтобы запускать события и сообщать нужным подписчикам, какие события были запущены. Агрегатор событий не заботится о том, что происходит после того, как событие было отработано, чего нельзя сказать о медиаторе.

Хорошим примером посредника является интерфейс типа wizard. Допустим, у вас есть большой процесс регистрации для системы, над которой вы работали. Часто, когда от пользователя требуется много информации, рекомендуется разбить его на несколько этапов.

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

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

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

Шаблон фасада

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

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

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

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

Следующие шаги

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

Ссылка
скопирована
Получите бесплатные уроки на наших курсах
Все курсы
Программирование
Скидка 25%
Фронтенд-разработчик с нуля
Погрузитесь в мир веб-разработки, освоив основные инструменты работы: HTML, CSS, JavaScript. Научитесь работать с дизайн-макетами и адаптивной версткой, сверстаете свои первые страницы и поймете, как строить карьерный трек в ИТ.
Получи бесплатный
вводный урок!
Пожалуйста, укажите корректный e-mail
отправили вводный урок на твой e-mail!
Получи все материалы в telegram и ускорь обучение!
img
Еще по теме:
img
В этой статье обсудим один из важнейших аргументов функции, который ТЫ, мой друг, будешь использовать в каждом своем боте.  Ты с
img
Введение    Настало время глубже погрузиться во взаимодействие человека с ботом. Сегодня изучим декоратор message_handler(). Узн
img
Погружение в aiogram (#5 Отправка стикеров)   Введение   Продолжаем изучать функционал библиотеки aiogram для работы с Telegram
img
Гипервизор - это программное обеспечение для виртуализации, используемое для создания и запуска виртуальных машин (ВМ). Гипервиз
img
Виртуализация серверов позволяет запускать несколько виртуальных машин на одном физическом сервере. Запуск виртуальных машин (ВМ
img
Сегодня мы рассмотрим, как настроить и использовать PHP в проекте. Но прежде чем начать, нужно понять, что такое PHP. Что такое
ЗИМНИЕ СКИДКИ
40%
50%
60%
До конца акции: 30 дней 24 : 59 : 59