Статьи

Пример шаблона State State

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

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

1. Введение

Чтобы проиллюстрировать использование Государственного шаблона проектирования, давайте поможем компании, которая стремится создать робота для приготовления пищи. Компания хочет простого робота, который может просто ходить и готовить. Пользователь может управлять роботом, используя набор команд через дистанционное управление. В настоящее время робот может делать три вещи: он может ходить, готовить или может быть выключен.

Компания установила протоколы для определения функциональности робота. Если робот находится во включенном состоянии, вы можете дать ему команду ходить. Если будет предложено готовить, состояние изменится на «готовить» или, если установлено «выключено», оно будет отключено.

Аналогично, в состоянии «готовить» он может ходить или готовить, но не может быть выключен. И, наконец, когда он находится в состоянии «выключено», он автоматически включается и начинает ходить, когда пользователь дает команду ходить, но не может готовить в выключенном состоянии.

Это может выглядеть как простая реализация: класс роботов с набором методов, таких как «ходить», «готовить», «выключать» и состояниями «включено», «готовить» и «выключать». Мы можем использовать ветки if-else или switch для реализации протоколов, установленных компанией. Но слишком много операторов if-else или switch создаст кошмар обслуживания, поскольку в будущем сложность может возрасти.

Вы можете подумать, что мы можем реализовать это без проблем, используя операторы if-else, но по мере изменения код станет более сложным. Требование ясно показывает, что поведение объекта действительно основано на состоянии этого объекта. Мы можем использовать шаблон разработки состояний, который инкапсулирует состояния объекта в другой отдельный класс и сохраняет контекстный класс независимым от любого изменения состояния.

Давайте сначала узнаем о State Design Pattern, а затем внедрим его для решения вышеуказанной проблемы.

2. Что такое государственный шаблон проектирования

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

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

Шаблон состояний полезен при разработке эффективной структуры для класса, типичный экземпляр которого может существовать во многих различных состояниях и демонстрировать различное поведение в зависимости от состояния, в котором он находится. Другими словами, в случае объекта такого типа класс, некоторые или все его поведение полностью зависит от его текущего состояния. В терминологии шаблона проектирования State такой класс называется классом Context . Объект Context может изменить свое поведение при изменении его внутреннего состояния и также называется объектом с состоянием.

Шаблон State предлагает перенести поведение, зависящее от состояния, из класса Context в набор отдельных классов, называемых классами State . Каждое из множества различных состояний, в которых может существовать объект Context может отображаться в отдельный класс State . Реализация класса State содержит поведение контекста, характерное для данного состояния, а не общее поведение самого контекста. Контекст действует как клиент для набора объектов State в том смысле, что он использует различные объекты State для предоставления необходимого поведения, специфичного для состояния, объекту приложения, который использует контекст бесшовным образом.

Инкапсулируя поведение, зависящее от состояния, в отдельных классах, контекстная реализация становится проще для чтения: без слишком многих условных операторов, таких как конструкции if-else или switch-case. Когда объект Context создается впервые, он инициализирует себя своим начальным объектом State. Этот объект состояния становится текущим объектом состояния для контекста. При замене текущего объекта State новым объектом State контекст переходит в новое состояние.

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

Рисунок 1 - Диаграмма классов

Рисунок 1 — Диаграмма классов

контекст

  • Определяет интерфейс, представляющий интерес для клиентов.
  • Поддерживает экземпляр подкласса ConcreteState который определяет текущее состояние.

государство

  • Определяет интерфейс для инкапсуляции поведения, связанного с конкретным состоянием контекста.

ConcreteState подклассы

  • Каждый подкласс реализует поведение, связанное с состоянием Context .

3. Реализация государственного шаблона проектирования

Ниже RoboticState интерфейс RoboticState который содержит поведение робота.

1
2
3
4
5
6
7
8
9
package com.javacodegeeks.patterns.statepattern;
 
public interface RoboticState {
 
    public void walk();
    public void cook();
    public void off();
 
}

Класс Robot — это конкретный класс, реализующий интерфейс RoboticState . Класс содержит набор всех возможных состояний, в которых может находиться робот.

01
02
03
04
05
06
07
08
09
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
package com.javacodegeeks.patterns.statepattern;
 
public class Robot implements RoboticState{
 
    private RoboticState roboticOn;
    private RoboticState roboticCook;
    private RoboticState roboticOff;
 
    private RoboticState state;
 
    public Robot(){
        this.roboticOn = new RoboticOn(this);
        this.roboticCook = new RoboticCook(this);
        this.roboticOff = new RoboticOff(this);
 
        this.state = roboticOn;
    }
 
    public void setRoboticState(RoboticState state){
        this.state = state;
    }
 
    @Override
    public void walk() {
        state.walk();
    }
 
    @Override
    public void cook() {
        state.cook();
 
    }
 
    @Override
    public void off() {
        state.off();
    }
 
    public RoboticState getRoboticOn() {
        return roboticOn;
    }
 
    public void setRoboticOn(RoboticState roboticOn) {
        this.roboticOn = roboticOn;
    }
 
    public RoboticState getRoboticCook() {
        return roboticCook;
    }
 
    public void setRoboticCook(RoboticState roboticCook) {
        this.roboticCook = roboticCook;
    }
 
    public RoboticState getRoboticOff() {
        return roboticOff;
    }
 
    public void setRoboticOff(RoboticState roboticOff) {
        this.roboticOff = roboticOff;
    }
 
    public RoboticState getState() {
        return state;
    }
 
    public void setState(RoboticState state) {
        this.state = state;
    }
 
}

Класс инициализирует все состояния и устанавливает текущее состояние как включенное.

Теперь мы увидим все конкретные состояния робота. Робот будет в любом из этих состояний в любое время.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.javacodegeeks.patterns.statepattern;
 
public class RoboticOn implements RoboticState{
 
    private final Robot robot;
 
    public RoboticOn(Robot robot){
        this.robot = robot;
    }
 
    @Override
    public void walk() {
        System.out.println("Walking...");
    }
 
    @Override
    public void cook() {
        System.out.println("Cooking...");
        robot.setRoboticState(robot.getRoboticCook());
    }
 
    @Override
    public void off() {
        robot.setState(robot.getRoboticOff());
        System.out.println("Robot is switched off");
 
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.javacodegeeks.patterns.statepattern;
 
public class RoboticCook implements RoboticState{
 
    private final Robot robot;
 
    public RoboticCook(Robot robot){
        this.robot = robot;
    }
 
    @Override
    public void walk() {
        System.out.println("Walking...");
        robot.setRoboticState(robot.getRoboticOn());
    }
 
    @Override
    public void cook() {
        System.out.println("Cooking...");
    }
 
    @Override
    public void off() {
        System.out.println("Cannot switched off while cooking...");
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.javacodegeeks.patterns.statepattern;
 
public class RoboticOff implements RoboticState{
 
    private final Robot robot;
 
    public RoboticOff(Robot robot){
        this.robot = robot;
    }
 
    @Override
    public void walk() {
        System.out.println("Walking...");
        robot.setRoboticState(robot.getRoboticOn());
    }
 
    @Override
    public void cook() {
        System.out.println("Cannot cook at Off state.");
    }
 
    @Override
    public void off() {
        System.out.println("Already switched off...");
    }
}

Теперь давайте проверим код.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package com.javacodegeeks.patterns.statepattern;
 
public class TestStatePattern {
 
    public static void main(String[] args) {
        Robot robot = new Robot();
        robot.walk();
        robot.cook();
        robot.walk();
        robot.off();
 
        robot.walk();
        robot.off();
        robot.cook();
 
    }
 
}

Приведенный выше код приведет к следующему выводу:

1
2
3
4
5
6
7
Walking...
Cooking...
Walking...
Robot is switched off
Walking...
Robot is switched off
Cannot cook at Off state.

В приведенном выше примере мы увидели, что инкапсуляция состояний объекта в разные классы делает код управляемым и гибким.

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

Для реализации всего этого нам нужно ввести новый класс состояний и включить это состояние в класс Robot . Ниже приведены изменения.

01
02
03
04
05
06
07
08
09
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
package com.javacodegeeks.patterns.statepattern;
 
public class Robot implements RoboticState{
 
    private RoboticState roboticOn;
    private RoboticState roboticCook;
    private RoboticState roboticOff;
    private RoboticState roboticStandby;
 
    private RoboticState state;
 
    public Robot(){
        this.roboticOn = new RoboticOn(this);
        this.roboticCook = new RoboticCook(this);
        this.roboticOff = new RoboticOff(this);
        this.roboticStandby = new RoboticStandby(this);
 
        this.state = roboticOn;
    }
 
    public void setRoboticState(RoboticState state){
        this.state = state;
    }
 
    @Override
    public void walk() {
        state.walk();
        setState(getRoboticStandby());
    }
 
    @Override
    public void cook() {
        state.cook();
        setState(getRoboticStandby());
    }
 
    @Override
    public void off() {
        state.off();
    }
 
    public RoboticState getRoboticOn() {
        return roboticOn;
    }
 
    public void setRoboticOn(RoboticState roboticOn) {
        this.roboticOn = roboticOn;
    }
 
    public RoboticState getRoboticCook() {
        return roboticCook;
    }
 
    public void setRoboticCook(RoboticState roboticCook) {
        this.roboticCook = roboticCook;
    }
 
    public RoboticState getRoboticOff() {
        return roboticOff;
    }
 
    public void setRoboticOff(RoboticState roboticOff) {
        this.roboticOff = roboticOff;
    }
 
    public RoboticState getState() {
        return state;
    }
 
    public void setState(RoboticState state) {
        this.state = state;
    }
 
    public RoboticState getRoboticStandby() {
        return roboticStandby;
    }
 
    public void setRoboticStandby(RoboticState roboticStandby) {
        this.roboticStandby = roboticStandby;
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.javacodegeeks.patterns.statepattern;
 
public class RoboticStandby implements RoboticState{
 
private final Robot robot;
 
    public RoboticStandby(Robot robot){
        this.robot = robot;
    }
 
    @Override
    public void walk() {
        System.out.println("In standby state...");
        robot.setState(robot.getRoboticOn());
        System.out.println("Walking...");
    }
 
    @Override
    public void cook() {
        System.out.println("In standby state...");
        robot.setRoboticState(robot.getRoboticCook());
        System.out.println("Cooking...");
    }
 
    @Override
    public void off() {
        System.out.println("In standby state...");
        robot.setState(robot.getRoboticOff());
        System.out.println("Robot is switched off");
 
    }
 
}

Теперь приведенное выше изменение кода приведет к следующему выводу:

01
02
03
04
05
06
07
08
09
10
11
Walking...
In standby state...
Cooking...
In standby state...
Walking...
In standby state...
Robot is switched off
Walking...
In standby state...
Robot is switched off
Cannot cook at Off state.

4. Когда использовать шаблон государственного дизайна

Используйте шаблон State в любом из следующих случаев:

  • Поведение объекта зависит от его состояния, и оно должно изменить свое поведение во время выполнения в зависимости от этого состояния.
  • Операции имеют большие составные условные операторы, которые зависят от состояния объекта. Это состояние обычно представлено одной или несколькими перечисляемыми константами. Часто несколько операций будут содержать одну и ту же условную структуру. Шаблон State помещает каждую ветвь условия в отдельный класс. Это позволяет вам трактовать состояние объекта как отдельного объекта, который может отличаться независимо от других объектов.

5. Государственный шаблон проектирования в Java

  • javax.faces.lifecycle.LifeCycle#execute()

6. Загрузите исходный код

Это был урок по государственному шаблону проектирования. Вы можете скачать исходный код здесь: StatePattern-Project