0 added
0 removed
Original
2026-01-01
Modified
2026-02-26
1
<p>Паттерн "Состояние" - яркий пример замены условных конструкций на полиморфизм подтипов. Он довольно широко используется и способен по-настоящему снизить сложность кода. Разберем его на примере поведения экранов телефонов.</p>
1
<p>Паттерн "Состояние" - яркий пример замены условных конструкций на полиморфизм подтипов. Он довольно широко используется и способен по-настоящему снизить сложность кода. Разберем его на примере поведения экранов телефонов.</p>
2
<p><em>Не все телефоны ведут себя одинаковым образом, но для урока надо было выбрать конкретный пример</em></p>
2
<p><em>Не все телефоны ведут себя одинаковым образом, но для урока надо было выбрать конкретный пример</em></p>
3
<p>Всего у телефона три базовых состояния:</p>
3
<p>Всего у телефона три базовых состояния:</p>
4
<ol><li>Телефон выключен. Экран не реагирует на прикосновения.</li>
4
<ol><li>Телефон выключен. Экран не реагирует на прикосновения.</li>
5
<li>Телефон включен, но экран выключен. Экран реагирует только на прикосновение (но не на смахивание) и включается.</li>
5
<li>Телефон включен, но экран выключен. Экран реагирует только на прикосновение (но не на смахивание) и включается.</li>
6
<li>Телефон включен и экран тоже. Реакция на прикосновения и жесты зависит от активного приложения.</li>
6
<li>Телефон включен и экран тоже. Реакция на прикосновения и жесты зависит от активного приложения.</li>
7
</ol><p>Смоделируем эту логику в классе, отвечающем за экран, и добавим туда два события: прикосновение (touch) и смахивание (swipe).</p>
7
</ol><p>Смоделируем эту логику в классе, отвечающем за экран, и добавим туда два события: прикосновение (touch) и смахивание (swipe).</p>
8
<p>Событий всего два, а уже сколько условных конструкций. В реальности событий было бы гораздо больше, и все они должны учитывать состояние активности телефона и экрана.</p>
8
<p>Событий всего два, а уже сколько условных конструкций. В реальности событий было бы гораздо больше, и все они должны учитывать состояние активности телефона и экрана.</p>
9
<p>Решая эту задачу в лоб, мы получим огромное количество условных конструкций в методе каждого события. Такой код очень сложен и хрупок. Изменение количества состояний и добавление новых событий чревато постоянными багами. Тяжело увидеть картину целиком и что-то не упустить.</p>
9
<p>Решая эту задачу в лоб, мы получим огромное количество условных конструкций в методе каждого события. Такой код очень сложен и хрупок. Изменение количества состояний и добавление новых событий чревато постоянными багами. Тяжело увидеть картину целиком и что-то не упустить.</p>
10
<p>Сложность такого кода можно значительно снизить за счет двух последовательных преобразований: выделения явного состояния и подключения полиморфизма подтипов.</p>
10
<p>Сложность такого кода можно значительно снизить за счет двух последовательных преобразований: выделения явного состояния и подключения полиморфизма подтипов.</p>
11
<h2>Явно выделенное состояние</h2>
11
<h2>Явно выделенное состояние</h2>
12
<p>Текущая реализация экрана опирается на флаги. В программировании так называют переменные содержащие булевы значения.</p>
12
<p>Текущая реализация экрана опирается на флаги. В программировании так называют переменные содержащие булевы значения.</p>
13
<p>Флаги часто (но не всегда!) - признак плохой архитектуры. Они имеют тенденцию множиться и пересекаться. Логика, завязанная на комбинации разных флагов, усложняет анализ кода:</p>
13
<p>Флаги часто (но не всегда!) - признак плохой архитектуры. Они имеют тенденцию множиться и пересекаться. Логика, завязанная на комбинации разных флагов, усложняет анализ кода:</p>
14
<p>Такой стиль программирования имеет свое название: "флаговое программирование". Так говорят про код, в котором трудно разобраться из-за наличия логики, завязанной на комбинацию флагов. А наличие флагов почти наверняка к этому приведет. Все дело в том, что количество состояний у систем, как правило, больше чем два. То есть одного флага никогда не будет достаточно.</p>
14
<p>Такой стиль программирования имеет свое название: "флаговое программирование". Так говорят про код, в котором трудно разобраться из-за наличия логики, завязанной на комбинацию флагов. А наличие флагов почти наверняка к этому приведет. Все дело в том, что количество состояний у систем, как правило, больше чем два. То есть одного флага никогда не будет достаточно.</p>
15
<p>От флагов возможно уйти, введя явное состояние системы. В нашем примере несложно заметить, что состояний всего три:</p>
15
<p>От флагов возможно уйти, введя явное состояние системы. В нашем примере несложно заметить, что состояний всего три:</p>
16
<ul><li>Power Off: Питание отключено (а значит и экран выключен).</li>
16
<ul><li>Power Off: Питание отключено (а значит и экран выключен).</li>
17
<li>Screen Disabled: Экран выключен (но питание включено).</li>
17
<li>Screen Disabled: Экран выключен (но питание включено).</li>
18
<li>Screen On: Экран включен.</li>
18
<li>Screen On: Экран включен.</li>
19
</ul><p>Следующий шаг, заменить флаги на одну переменную, которая хранит текущее состояние системы:</p>
19
</ul><p>Следующий шаг, заменить флаги на одну переменную, которая хранит текущее состояние системы:</p>
20
<p>Главное, что произошло в коде выше - пропали проверки на комбинацию флагов. Это не отменяет возможности проверок сразу по нескольким состояниям, но состояния системы понимать гораздо проще, чем наборы флагов.</p>
20
<p>Главное, что произошло в коде выше - пропали проверки на комбинацию флагов. Это не отменяет возможности проверок сразу по нескольким состояниям, но состояния системы понимать гораздо проще, чем наборы флагов.</p>
21
<h2>Классы Состояний</h2>
21
<h2>Классы Состояний</h2>
22
<p>Для избавления от условных конструкций понадобится полиморфизм. На базе чего его строить? Благодаря наличию явно выделенного состояния легко увидеть зависимость поведения от состояния. Именно состояния должны трансформироваться в классы со своим собственным поведением, специфичным для данного состояния.</p>
22
<p>Для избавления от условных конструкций понадобится полиморфизм. На базе чего его строить? Благодаря наличию явно выделенного состояния легко увидеть зависимость поведения от состояния. Именно состояния должны трансформироваться в классы со своим собственным поведением, специфичным для данного состояния.</p>
23
<p>Экран, в свою очередь, избавится от всех проверок и начнет взаимодействовать с состояниями:</p>
23
<p>Экран, в свою очередь, избавится от всех проверок и начнет взаимодействовать с состояниями:</p>
24
<p>Теперь экран не делает ровным счетом ничего. Весь его код - это инициализация начального состояния и передача управления текущему активному состоянию. Как же выглядят классы состояний?</p>
24
<p>Теперь экран не делает ровным счетом ничего. Весь его код - это инициализация начального состояния и передача управления текущему активному состоянию. Как же выглядят классы состояний?</p>
25
<p>Проще всех устроено состояние выключенного телефона. В этом состоянии нет никакой реакции, поэтому методы пустые. Посмотрим ScreenDisabledState:</p>
25
<p>Проще всех устроено состояние выключенного телефона. В этом состоянии нет никакой реакции, поэтому методы пустые. Посмотрим ScreenDisabledState:</p>
26
<p>Прикосновение к экрану оживляет его. Для этого состояние ScreenDisabledState должно выполнить переход в состояние ScreenOnState. Именно поэтому внутрь каждого состояния передавался сам экран. Иначе невозможно было бы его изменять.</p>
26
<p>Прикосновение к экрану оживляет его. Для этого состояние ScreenDisabledState должно выполнить переход в состояние ScreenOnState. Именно поэтому внутрь каждого состояния передавался сам экран. Иначе невозможно было бы его изменять.</p>
27
<p>И последнее состояние ScreenOnState. Это единственное состояние, в котором происходит взаимодействие с программами</p>
27
<p>И последнее состояние ScreenOnState. Это единственное состояние, в котором происходит взаимодействие с программами</p>
28
<p>Это невероятно, но в коде больше не осталось ни одной условной конструкции. Стало легко видеть поведение телефона на все события в конкретном состоянии. Достаточно открыть нужный класс. Цена за такое удобство - большее количество файлов и кода.</p>
28
<p>Это невероятно, но в коде больше не осталось ни одной условной конструкции. Стало легко видеть поведение телефона на все события в конкретном состоянии. Достаточно открыть нужный класс. Цена за такое удобство - большее количество файлов и кода.</p>
29
<p>Очень важно не упустить главную идею паттерна. Классы состояний введены только для введения полиморфизма, но у них нет собственных данных для работы. В конечном итоге, все воздействие идет на сам экран, ту сущность, которую мы упрощаем.</p>
29
<p>Очень важно не упустить главную идею паттерна. Классы состояний введены только для введения полиморфизма, но у них нет собственных данных для работы. В конечном итоге, все воздействие идет на сам экран, ту сущность, которую мы упрощаем.</p>