JS: Полиморфизм
Теория: Паттерн Состояние (State)
Паттерн «Состояние» — яркий пример замены условных конструкций на полиморфизм подтипов. Он довольно широко используется и способен по-настоящему снизить сложность кода. Разберем его на примере поведения экранов телефонов.
Не все телефоны ведут себя одинаковым образом, но для урока надо было выбрать конкретный пример
Всего у телефона три базовых состояния:
- Телефон выключен. Экран не реагирует на прикосновения.
- Телефон включен, но экран выключен. Экран реагирует только на прикосновение (но не на смахивание) и включается.
- Телефон включен и экран тоже. Реакция на прикосновения и жесты зависит от активного приложения.
Смоделируем эту логику в классе, отвечающем за экран, и добавим туда два события: прикосновение (touch) и смахивание (swipe).
Событий всего два, а уже сколько условных конструкций. В реальности событий было бы гораздо больше, и все они должны учитывать состояние активности телефона и экрана.
Решая эту задачу в лоб, мы получим огромное количество условных конструкций в методе каждого события. Такой код очень сложен и хрупок. Изменение количества состояний и добавление новых событий чревато постоянными багами. Тяжело увидеть картину целиком и что-то не упустить.
Сложность такого кода можно значительно снизить за счет двух последовательных преобразований: выделения явного состояния и подключения полиморфизма подтипов.
Явно выделенное состояние
Текущая реализация экрана опирается на флаги. В программировании так называют переменные содержащие булевы значения.
Флаги часто (но не всегда!) — признак плохой архитектуры. Они имеют тенденцию множиться и пересекаться. Логика, завязанная на комбинации разных флагов, усложняет анализ кода:
Такой стиль программирования имеет свое название: «флаговое программирование». Так говорят про код, в котором трудно разобраться из-за наличия логики, завязанной на комбинацию флагов. А наличие флагов почти наверняка к этому приведет. Все дело в том, что количество состояний у систем, как правило, больше чем два. То есть одного флага никогда не будет достаточно.
От флагов возможно уйти, введя явное состояние системы. В нашем примере несложно заметить, что состояний всего три:
- Power Off: Питание отключено (а значит и экран выключен).
- Screen Disabled: Экран выключен (но питание включено).
- Screen On: Экран включен.
Следующий шаг, заменить флаги на одну переменную, которая хранит текущее состояние системы:
Главное, что произошло в коде выше – пропали проверки на комбинацию флагов. Это не отменяет возможности проверок сразу по нескольким состояниям, но состояния системы понимать гораздо проще, чем наборы флагов.
Классы Состояний
Для избавления от условных конструкций понадобится полиморфизм. На базе чего его строить? Благодаря наличию явно выделенного состояния легко увидеть зависимость поведения от состояния. Именно состояния должны трансформироваться в классы со своим собственным поведением, специфичным для данного состояния.
Экран, в свою очередь, избавится от всех проверок и начнет взаимодействовать с состояниями:
Теперь экран не делает ровным счетом ничего. Весь его код — это инициализация начального состояния и передача управления текущему активному состоянию. Как же выглядят классы состояний?
Проще всех устроено состояние выключенного телефона. В этом состоянии нет никакой реакции, поэтому методы пустые. Посмотрим ScreenDisabledState:
Прикосновение к экрану оживляет его. Для этого состояние ScreenDisabledState должно выполнить переход в состояние ScreenOnState. Именно поэтому внутрь каждого состояния передавался сам экран. Иначе невозможно было бы его изменять.
И последнее состояние ScreenOnState. Это единственное состояние, в котором происходит взаимодействие с программами
Это невероятно, но в коде больше не осталось ни одной условной конструкции. Стало легко видеть поведение телефона на все события в конкретном состоянии. Достаточно открыть нужный класс. Цена за такое удобство — большее количество файлов и кода.
Очень важно не упустить главную идею паттерна. Классы состояний введены только для введения полиморфизма, но у них нет собственных данных для работы. В конечном итоге, все воздействие идет на сам экран, ту сущность, которую мы упрощаем.





