Найти тему
Сделай игру

Как написать большое приложение

Оглавление

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

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

Не требуйте много от коалы - сам факт, что она пишет код уже заслуживает уважения!
Не требуйте много от коалы - сам факт, что она пишет код уже заслуживает уважения!

Сначала небольшой пример проблемы

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

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

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

Попробуем в своё

Итак, давайте попробуем создать своё приложение. Я специально выбрал нечто такое, с чем не имел дело никогда, чтобы допустить, по возможности, как можно больше ошибок. Давайте сделаем текстовый редактор. Ну, не целиком, а принципиальный скелет приложения.

Шаг первый, что это будет

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

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

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

Шаг второй, как это может развиваться

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

Начало реализации

Исходя из текущих и потенциальных потребностей, накидаем первую структуру приложения (буду использовать js для написания кода, но код тут, разумеется, может быть любым). Будем считать, что приложение будет работать в Electron.

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

Начальный код приложения
Начальный код приложения

Итак, тут мы отслеживаем запуск приложения, выход из приложения. Но есть одна сложность: предполагается, что будут какие-то общие данные, которые будут использоваться на всех уровнях (состояние приложения). Давайте его добавим; оно позволит более гибко передавать состояния между компонентами приложения и этапами работы приложения.

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

Код расширился:

Добавили объект состояния
Добавили объект состояния

Добавляем компоненты

Итак, как было сказано ранее, у нас есть 3 разных компонента, вот они:

  1. Компонент настроек приложения (А);
  2. Компонент панели управления (Б);
  3. Компонент ввода/вывода форматированного текста (В).

Их надо тоже связать - сделаем это следующим образом:

  • В будет получать управление от Б и сообщать ему о текущем состоянии текста под курсором;
  • Б будет транслировать все правила в В и обновлять отображение, если оттуда пришла команда на смену формата;
  • А будет транслироваться в Б;
  • Также есть возможность частично или полностью изменить настройки по умолчанию, установив какие-то свои.

Чтобы всё это реализовать - свяжем компоненты напрямую.

Добавили инициализацию компонентов и связность
Добавили инициализацию компонентов и связность

Как можно заметить, я связал компоненты напрямую, то есть просто передал компонент целиком. Это не очень хорошая практика, так как даёт слишком большой контроль. Можно тут пойти по более безопасному пути - передавать не весь компонент целиком, а некоторую выделенную структуру (скажем panel.sharedDocFunctionality()), позволяющую уведомлять зависимый компонент об изменениях, но не управлять им.

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

Запуск приложения

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

Его основной задачей будет:

  • Загрузить документ или создать новый;
  • Установить режимы работы с документом (чтение, чтение-запись, может какие-то прочие режимы);
  • Направлять ввод с клавиатуры или мыши на компоненты;
  • Обрабатывать структуру документа, взаимодействовать с файловой системой;
  • Обеспечивать возможность интеграции дополнительных компонентов;
  • И, наконец, отрисовать всё на экране.
Добавили управляющего приложением
Добавили управляющего приложением

Заметьте, функция render - асинхронная; то есть она будет выполняться так долго, как долго пользователь будет работать с приложением. Внутри будет уже отдельная история связи компонентов.

Структура документа

Пару слов надо сказать и про неё. Думаю, это будет просто xml документ, который будет налету разбираться или создаваться и отображаться. Храниться на диске он тоже будет как xml документ, но часть данных может храниться в обёрнутом в base64 бинарном виде (например, встраиваемые изображения).

Заключение

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

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