Метафора
В реальной жизни каждый человек может прибывать в разных состояниях. Если вы устали, то на предложение «Сходи в магазин», вы скорее ответите «Не пойду, я устал». А если вы не устали, тогда скорее всего вы ответите «Уже иду». Таким образом, ваше поведение зависит от того, в каком состоянии Вы прибываете.
Назначение
Используется в тех случаях, когда:
- во время выполнения программы объект должен менять своё поведение в зависимости от своего состояния,
- в коде методов объекта используются многочисленные условные конструкции, выбор которых зависит от текущего состояния объекта.
Паттерн может быть своего рода представлением в виде ООП реализации конечного автомата (см. пример).
Диаграмма
Context делегирует операции по переходу между состояниями объектам State, что позволяет перейти из одного конкретного состояния ConcreteState1 в другое ConcreteState2.
Благодаря паттерну мы решаем проблему множества условных конструкций в коде, где будет выполняться та или иная логика в зависимости от анализа состояния объекта.
В описании паттерна GOF не говорится где именно определяется условие перехода в новое состояние. Существует два варианта: класс Context или подклассы State. Преимущество последнего варианта заключается в простоте добавления новых производных классов. Недостаток заключается в том, что каждый подкласс State для осуществления перехода в новое состояние должен знать о своих соседях, что вводит зависимости между подклассами.
Пример
До паттерна:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
CLASS lcl_water DEFINITION. PUBLIC SECTION. CONSTANTS: gc_state_solid TYPE i VALUE 1, gc_state_liquid TYPE i VALUE 2, gc_state_gaz TYPE i VALUE 3. METHODS: constructor IMPORTING iv_state TYPE i, heat, frost. PRIVATE SECTION. DATA: mv_state TYPE i. ENDCLASS. CLASS lcl_water IMPLEMENTATION. METHOD constructor. mv_state = iv_state. ENDMETHOD. METHOD heat. CASE mv_state. WHEN gc_state_solid. WRITE: / 'Превращаем лёд в жидкость'. mv_state = gc_state_liquid. WHEN gc_state_liquid. WRITE: / 'Превращаем жидкость в пар'. mv_state = gc_state_gaz. WHEN gc_state_gaz. WRITE: / 'Повышаем температуру пара'. ENDCASE. ENDMETHOD. METHOD frost. CASE mv_state. WHEN gc_state_liquid. WRITE: / 'Превращаем жидкость в лёд'. mv_state = gc_state_solid. WHEN gc_state_gaz. WRITE: / 'Превращаем пар в жидкость'. mv_state = gc_state_liquid. ENDCASE. ENDMETHOD. ENDCLASS. START-OF-SELECTION. DATA(lo_water) = NEW lcl_water( lcl_water=>gc_state_liquid ). lo_water->heat( ). lo_water->heat( ). lo_water->frost( ). lo_water->frost( ). |
Класс lcl_water изменяет своё поведение в зависимости от состояния, но решение получается сложным, т.к. нам при добавлении нового состояния нам придётся влезать во все CASE конструкции. Попробуем применить State паттерн:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
*&---------------------------------------------------------------------* *& Определение классов шаблона *&---------------------------------------------------------------------* CLASS lcl_water DEFINITION DEFERRED. INTERFACE lif_water_state. CONSTANTS: gc_state_solid TYPE i VALUE 1, gc_state_liquid TYPE i VALUE 2, gc_state_gaz TYPE i VALUE 3. METHODS: heat IMPORTING io_water TYPE REF TO lcl_water OPTIONAL, frost IMPORTING io_water TYPE REF TO lcl_water OPTIONAL. ENDINTERFACE. CLASS lcl_water DEFINITION. PUBLIC SECTION. INTERFACES: lif_water_state. ALIASES: heat FOR lif_water_state~heat, frost FOR lif_water_state~frost. METHODS: constructor IMPORTING io_state TYPE REF TO lif_water_state, set_state IMPORTING io_state TYPE REF TO lif_water_state. PRIVATE SECTION. DATA: mo_state TYPE REF TO lif_water_state. ENDCLASS. CLASS lcl_water IMPLEMENTATION. METHOD constructor. set_state( io_state ). ENDMETHOD. METHOD heat. mo_state->heat( me ). ENDMETHOD. METHOD frost. mo_state->frost( me ). ENDMETHOD. METHOD set_state. mo_state = io_state. ENDMETHOD. ENDCLASS. CLASS lcl_liquid_water_state DEFINITION. PUBLIC SECTION. INTERFACES: lif_water_state. ALIASES: heat FOR lif_water_state~heat, frost FOR lif_water_state~frost. ENDCLASS. CLASS lcl_solid_water_state DEFINITION. PUBLIC SECTION. INTERFACES: lif_water_state. ALIASES: heat FOR lif_water_state~heat, frost FOR lif_water_state~frost. ENDCLASS. CLASS lcl_gaz_water_state DEFINITION. PUBLIC SECTION. INTERFACES: lif_water_state. ALIASES: heat FOR lif_water_state~heat, frost FOR lif_water_state~frost. ENDCLASS. CLASS lcl_liquid_water_state IMPLEMENTATION. METHOD heat. WRITE: / 'Превращаем воду в пар'. io_water->set_state( io_state = CAST #( NEW lcl_gaz_water_state( ) ) ). ENDMETHOD. METHOD frost. WRITE: / 'Превращаем воду в лёд'. io_water->set_state( io_state = CAST #( NEW lcl_solid_water_state( ) ) ). ENDMETHOD. ENDCLASS. CLASS lcl_solid_water_state IMPLEMENTATION. METHOD heat. WRITE: / 'Превращаем лед в жидкость'. io_water->set_state( io_state = CAST #( NEW lcl_liquid_water_state( ) ) ). ENDMETHOD. METHOD frost. WRITE: / 'Замораживаем лёд еще раз'. ENDMETHOD. ENDCLASS. CLASS lcl_gaz_water_state IMPLEMENTATION. METHOD heat. WRITE: / 'Повышаем температуру пара'. ENDMETHOD. METHOD frost. WRITE: / 'Превращаем пар в воду'. io_water->set_state( io_state = CAST #( NEW lcl_liquid_water_state( ) ) ). ENDMETHOD. ENDCLASS. *&---------------------------------------------------------------------* *& Работа с классами шаблона *&---------------------------------------------------------------------* START-OF-SELECTION. DATA(lo_water) = NEW lcl_water( NEW lcl_liquid_water_state( ) ). lo_water->heat( ). lo_water->heat( ). lo_water->frost( ). lo_water->frost( ). |
Реализовав паттерн «Состояние» мы вынесли поведение, зависящее от текущего состояния объекта, в отдельные классы, и избежали перегруженности методов объекта условными конструкциями. Кроме того, при необходимости мы можем ввести в систему новые классы состояний.