Прекратите помещать состояние в ваши модели просмотра

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

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

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

Рассмотрим пример.

Состояние приложения внутри модели представления

Представьте себе приложение с простой функцией загрузки файлов.

  1. Проверьте, есть ли файл для загрузки. Если да, покажите кнопку загрузки.
  2. После нажатия кнопки загрузки начните загрузку.

Видите состояние? Вот он снова, но на этот раз из интерфейса API:

И соответствующие модели:

Где государство? Между двумя сервисными вызовами! DownloadApi.search() и DownloadApi.downloadFile().

Теперь можно указать дополнительные шаги пользовательского интерфейса (включая состояние):

  1. Вызовите DownloadApi.search() и посмотрите, есть ли у возвращенного FileSearchResult файл для загрузки.
  2. Если файл не существует, покажите пустой пользовательский интерфейс.
  3. Если есть файл для загрузки, запомните идентификатор файла.
  4. При нажатии кнопки загрузки передайте идентификатор файла в DownloadApi.downloadFile(), чтобы начать загрузку.

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

else {
  // This will never happen.
}

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

(Плохая) модель просмотра

Наивным местом для хранения RemoteFile результатов поиска была бы модель представления сразу после того, как они были возвращены из DownloadApi.search().

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

Это логическая ошибка или проблема с уровнем данных? Есть ли возможность выздоровления, и если да, то как? А пока что вы скажете пользователю?

Состояние делегата, не управляйте им

В модели представления метод startDownload() можно переписать:

Когда поле RemoteFile? не указано, методу startDownload() не нужно беспокоиться о внутреннем состоянии, допускающем значение NULL. Он передается напрямую, когда модели представления необходимо отреагировать на событие.

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

Куда идет штат?

К модели представления примыкают два слоя:

  1. Слой просмотра (подходит для переходного состояния)
  2. Уровень данных (подходит для постоянного состояния)

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

  • Безопасно ли сохранять это состояние как временное взаимодействие для пользователя? Подумайте EditText, CheckBox или другие элементы пользовательского интерфейса, которые, возможно, потребуется обновить при воссоздании пользовательского интерфейса.
  • Безопасно ли терять это состояние? Представляет ли он что-то, что должно пережить пользовательский интерфейс, или его можно легко восстановить из источника при создании нового пользовательского интерфейса?

Уровень данных может явно записывать все, что вас интересует, так что наблюдающая за ним модель представления может подталкивать новые состояния к пользовательскому интерфейсу, точно отражая все, что вам нужно сохранить. Примером может быть файл, который несколько пользователей могут обновлять удаленно, или состояние фоновой загрузки, не соответствующее какому-либо пользовательскому интерфейсу. Наличие или отсутствие RemoteFile также можно смоделировать на уровне данных!

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

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

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

Принимайте решения без гражданства, а ваше состояние - без принятия решений.

Коллин обрабатывает состояния в Livefront, и он благодарит вас за чтение.