Видео курс Шаблоны проектирования

Основные темы, рассматриваемые на уроке:
00:25 1 Метафора
02:55 2 Структура паттерна на языке UML
03:41 3 Структура паттерна на языке C#
06:40 4 О понимании Конечного автомата (книга)
08:30 5 Назначение паттерна

Видео урок расскажет о шаблоне проектирования State (Состояние), который позволяет объекту изменять свое поведение в зависимости от своего состояния. Поведение объекта изменяется на столько, что создается впечатление, что изменился класс объекта.

Для просмотра полной версии видеокурса и получения доступа к дополнительным учебным материалам Вам необходимо оформить подписку
Оформить подписку

Паттерн State

Название

Состояние

Также известен как

FSM (Finite-State Machine) - КА (Конечный автомат или Машина состояний)

Классификация

По цели: поведенческий

По применимости: к объектам

Частота использования

Средняя               -   1 2 3 4 5

Назначение

Паттерн State – позволяет объекту изменять свое поведение в зависимости от своего состояния. Поведение объекта изменяется на столько, что создается впечатление, что изменился класс объекта.

Введение

Паттерн State описывает способы и правила построения объектно-ориентированного представления конечных автоматов (КА). Конечные автоматы классифицируются по логическим свойствам функций переходов и выходов, а также по характеру отсчёта дискретного времени.

По способу формирования функций выходов выделяют автоматы Мили и Мура. У автоматов Мили функции выходов находятся на ребре, а у автоматов Мура функции выходов находятся в состоянии. По характеру отсчёта дискретного времени автоматы делятся на синхронные и асинхронные.

Конечный автомат Мили

Рассмотрим простейший тип конечно-автоматного преобразователя информации: Автомат Мили. Определим конечный автомат формально. Есть и другие определения, но мы остановимся на этом.

Задавать автомат удобно графом, в котором вершины соответствуют состояниям, а ребро из состояния sn в состояние sm, помеченное x/y, проводится тогда, когда автомат из состояния sn под воздействием входного сигнала x переходит в состояние sm с выходной реакцией y.

ПРИМЕР:

Зададим конечный автомат, который имеет:

  • четыре состояния, S = {s0, s1, s2, s3} 
  • два входных сигнала X = {x0, x1}, где: x0 = 0, x1 = 1
  • шесть выходных сигналов Y = {y0, y1, y2, y3, y4, y5} где: y0 = 1, y1 = 2, y2 = 3, y3 = 4, y4 = 5, y5 = 6.

Теперь представим автомат в виде графа:

Кроме графического представления автомата, можно использовать и табличное, задавая функции переходов и выходов в виде таблиц. Представим данный автомат таблично. Таблица 1 - определяет функцию переходов автомата из одного состояния в другое.

Функция переходов δ (sn, xn) определяется так: δ (s0, 0) = s1; δ (s2, 1) = s0; ….

δ

0

1

s0

s1

s3

s1

s2

s0

s2

s2

s0

s3

s1

s3

 

Таблица 1.

Таблица 2 - определяет функцию выходов λ (sn, xn) так: λ (s0, 0) = y2; λ (s2, 1) = y3; ….

λ

0

1

s0

y2

y4

s1

y1

y3

s2

y0

y3

s3

y2

y5

 

Таблица 2.

Реализация конечного автомата Мили

Для программной реализации будет использоваться язык C# и технология WWF – как инструменты, упрощающие реализацию программных конечных автоматов. На рисунке 1, представлена блок-схема (WWF) программы, реализующей поведение автомата. 

Нетрудно увидеть, что топология блок-схемы программы повторяет топологию графа переходов конечного автомата. С каждым состоянием связана операция, выполняющая функцию ожидания очередного события прихода нового входного сигнала и чтение его в стандартный буфер – int x, а также последующий анализ того, какой это сигнал. В зависимости от того, какой сигнал пришел на вход, выполняется та или иная функция y0y5 и происходит переход к новому состоянию. Построив программу подобную этой и добавив активные устройства, реализующие отдельные входные и выходные операции, можно управление каким-либо процессом поручить компьютеру.

См. пример к главе: \020_State\002_MealyStateMachine

Конечный автомат Мура

Рассмотрим еще один тип конечно-автоматного преобразователя информации: Автомат Мура. Автоматы Мура образуют другой класс моделей, с точки зрения вычислительной мощности полностью эквивалентных классу автоматов Мили. В автомате Мура: A = <S, X, Y, s0, δ, λ>, выходная функция λ определяется не на паре <состояние, входной сигнал>, а только на состоянии . Выход автомата Мура определяется однозначно тем состоянием, в которое автомат переходит после приема входного сигнала.  

ПРИМЕР:

Зададим конечный автомат Мура, который будет являться эквивалентным автомату Мили, рассмотренному выше. Не составит труда преобразовать исходный автомат Мили в автомат Мура.  На графе автомата Мили легко прослеживается взаимосвязь элементов входного и выходного алфавитов. 

Построив таблицу соответствий состояний автомата Мили алфавитным парам, легко увидеть какие состояния требуют расщепления.

 

 

x0

x1

Расщепление состояния S

s0

y3

y3

Не требуется

s1

y2

y2

Не требуется

s2

y0

y1

s2 = {s2, s4}

s3

y4

y5

s3 = {s3, s5}

 

Таблица 3.

 

Состояния s2 и s3 автомата Мили расщепляются на два эквивалентных состояния: s2 = {s2, s4}, s3 = {s3, s5}, с каждым из которых связывается один выходной символ.

Автомат Мура будет иметь:

  • шесть состояний, S = {s0, s1, s2, s3, s4, s5} 
  • два входных сигнала X = {x0, x1}, где: x0 = 0, x1 = 1.
  • шесть выходных сигналов Y = {y0, y1, y2, y3, y4, y5} где: y0 = 1, y1 = 2, y2 = 3, y3 = 4, y4 = 5, y5 = 6.

 

Теперь представим автомат Мура в виде графа (в сравнении с автоматом Мили):

Реализация конечного автомата Мура

На рисунке 2, представлена блок-схема (WWF) программы реализующей поведение автомата Мура. 

Нетрудно увидеть, что топология блок-схемы программы повторяет топологию графа переходов конечного автомата. С каждым состоянием связана операция, выполнения определенных действий при наступлении данного состояния, ожидания прихода нового входного сигнала, чтение его в стандартный буфер – int x, а также последующий анализ того, какой это сигнал и перевод автомата в новое состояние.

См. пример к главе: \020_State\003_MooreStateMachine

Рассмотренные программные модели автоматов Мура и Мили - полностью эквивалентны.

Структура паттерна на языке UML

См. пример к главе: \020_State\001_State

Структура паттерна на языке C#

См. пример к главе: \020_State\001_State

Участники

  • Context - Контекст:

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

  • State - Состояние:

Задает интерфейс взаимодействия с «объектом-состоянием».

  • ConcreteState - Конкретное состояние:

Реализует поведение (функции выхода и функции перехода) ассоциированное с определенным состоянием. Очевидно, что данный паттерн реализует модель КА – Мура.

Отношения между участниками

Отношения между классами

  • Конкретные классы (ConcreteStateA и ConcreteStateB) связанны связью отношения наследования с абстрактным классом State.
  • Конкретный класс Context связан связью отношения агрегации с абстрактным классом State.

Отношения между объектами

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

Мотивация

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

Этот автомат имеет:

  • четыре состояния в которых может пребывать отец, S = { s0, s1, s2, s3 },  где:
  • s0 – Neutral (Нейтральное состояние)
  • s1 – Pity (Жалость)
  • s2 – Anger (Гнев)
  • s3 – Joy (Радость)
  • два входных сигнала – оценки полученные сыном в школе X = { x0, x1 }, где:
  • x0 = 2
  • x1 = 5
  • шесть выходных сигналов, т.е., действий отца  Y = { y0, y1, y2, y3, y4, y5 },  где:
    • y0 - брать ремень;
    • y1 - ругать сына;
    • y2 - успокаивать сына;
    • y3 - надеяться;
    • y4 - радоваться;
    • y5 - ликовать;

Сына, получившего одну и туже оценку – ожидает дома совершенно различная реакция отца в зависимости от предыстории его учебы. Отец помнит, как его сын учился раньше и строит модель воспитания с учетом успехов и неудач сына. Например, после третьей двойки в истории (2, 2, 2) сына встретят ремнем (y0), а в истории (2, 2, 5, 2) – будут успокаивать(y2).

Каждая предыстория определяет текущее состояние автомата, при этом некоторые входные предыстории эквивалентны (те истории которые приводят автомат в одно и тоже состояние), например, история (2, 2, 5) эквивалентна пустой истории, которой соответствует начальное состояние.

Представим данный автомат таблично. Таблица 4 - определяет функцию переходов автомата из одного состояния в другое.

Функция переходов δ (sn, xn) определяется так: δ (s0, 2) = s1; δ (s2, 5) = s0; ….

δ

2

5

s0 - Neutral

s1

s3

s1 - Pity

s2

s0

s2 - Anger

s2

s0

s3 - Joy

s1

s3

Таблица 4.

 

Таблица 5 - определяет функцию выходов λ (sn, xn) так: λ (s0, 2) = y2; λ (s2, 5) = y3; ….

λ

2

5

s0 - Neutral

y2 - успокаивать сына

y4 - радоваться

s1 - Pity

y1 - ругать сына

y3 - надеяться

s2 - Anger

y0 - брать ремень

y3 - надеяться

s3 - Joy

y2 - успокаивать сына

y5 - ликовать

Таблица 5.

Для упрощения программной реализации примера, рекомендуется преобразовать исходный автомат Мили в эквивалентный по вычислительной мощности автомат Мура. Для этого требуется произвести расщепление тех состояний - sn, которым соответствует более одной функции выхода - λ (sn, xn).

 

 

x0

x1

Расщепление состояния S

s0 - Neutral

y3

y3

Не требуется

s1 - Pity

y2

y2

Не требуется

s2 - Anger

y0

y1

s2 = {s2, s4}

s3 - Joy

y4

y5

s3 = {s3, s5}

Таблица 6.

Из таблицы преобразования видно, что произошло формирование двух новых состояний: {s4, s5}. Ранее в состоянии гнева – s2, отец либо ругал сына – y1, либо использовал ремень – y0, а в состоянии радости – s3, либо просто радовался – y4, либо ликовал – y5. Таким образом, функциональность можно распределить по состояниям согласно соответствия силы эмоциональной экспрессии состояния и выполняемого действия.

Например, s2-Гнев = {s2-Сильный Гнев, s4-Гнев}, s3-Радость = {s3-Радость, s5-Сильная Радость}. Соответственно: В состоянии «простого» гнева - s4-Гнев, можно ругать сына - y1 - ругать сына, а в состоянии сильного гнева - s2-Сильный Гнев, можно использовать ремень - y0 - брать ремень. В состоянии «простой» радости - s3-Радость, можно радоваться - y4 - радоваться, а в состоянии сильной радости - s5-Сильная Радость, можно ликовать - y5 - ликовать. Представим новый автомат таблично.

Таблица 7 - определяет функцию переходов автомата из одного состояния в другое.

δ

2

5

s0 - Neutral

s1

s3

s1 - Pity

s4

s0

s2 - Strong Anger

s2

s0

s3 - Joy

s1

s5

s4 -  Anger

s2

s0

s5Strong Joy

s1

s5

Таблица 7. (Функции переходов - δ (sn, xn))

Таблица 8 - определяет функцию выходов.

λ

2

5

s0 - Neutral

y2 - успокаивать сына

y4 - радоваться

s1 - Pity

y1 - ругать сына

y3 - надеяться

s2 - Strong Anger

y0 - брать ремень

y3 - надеяться

s3 - Joy

y2 - успокаивать сына

y5 - ликовать

s4 -  Anger

y0 - брать ремень

y3 - надеяться

s5Strong Joy

y2 - успокаивать сына

y5 - ликовать

Таблица 8. (Функции выходов - λ (sn, xn))

Теперь представим автомат Мура в виде графа (в сравнении с автоматом Мили):

Получившийся автомат Мура имеет:

  • шесть состояний в которых может пребывать отец, S = { s0, s1, s2, s3, s4, s5 },  где:
  • s0 – Neutral (Нейтральное состояние)
  • s1 – Pity (Жалость)
  • s2 – Strong Anger (Сильный Гнев)
  • s3 – Joy (Радость)
  • s4 – Anger (Гнев)
  • s5 – Strong Joy (Сильная Радость)
  • два входных сигнала – оценки полученные сыном в школе X = { x0, x1 }, где:
  • x0 = 2
  • x1 = 5
  • шесть выходных сигналов, т.е., действий отца  Y = { y0, y1, y2, y3, y4, y5 },  где:
    • y0 - брать ремень;
    • y1 - ругать сына;
    • y2 - успокаивать сына;
    • y3 - надеяться;
    • y4 - радоваться;
    • y5 - ликовать;

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

Предлагается более подробно рассмотреть программную реализацию автомата Мура, имитирующего поведение умного отца. 

Рассмотрим класс Father. Объект класса Father может находиться в одном из шести состояний: NeutralState (Нейтральное состояние), PityState (Жалость), JoyState (Радость), StrongJoyState (Сильная Радость), AngerState (Гнев), StrongAngerState (Сильный Гнев). Все классы состояний являются производными от базового абстрактного класса State. Объект типа Father хранит ссылку на определенный объект состояния.

public class Father
    {
        internal State State { get; set; }

        public Father()
        {
            State = new NeutralState();
        }

        public void FindOut(Mark mark)
        {
            State.HandleMark(this, mark);
        }
    }

Объект класса Father делегирует все запросы (вызовы метода HandleMark()), объекту типа State, ссылка на который храниться в автосвойстве State.

Основная идея паттерна State заключается в том, чтобы ввести абстрактный класс State для представления различных состояний некоторого объекта, в данном примере для представления различных состояний объекта типа Father. Класс State предоставляет интерфейс для всех производных классов, реализующих различные состояния отца, т.е., объекта типа Father.

internal abstract class State
    {
        internal virtual void HandleMark(Father father, Mark mark)
        {
            ChangeState(father, mark);
        }

        protected abstract void ChangeState(Father father, Mark mark);
    }

В подклассах класса State реализовано поведение, характерное для каждого конкретного состояния.

// Нейтральное состояние (S0)
    internal class NeutralState : State
    {
        internal NeutralState()
        {
            Console.WriteLine("Отец в нейтральном состоянии:");
            Hope();
        }

        protected override void ChangeState(Father father, Mark mark)
        {
            switch (mark)
            {
                case Mark.Two:
                    {
                        father.State = new PityState(); // S1
                        break;
                    }
                case Mark.Five:
                    {
                        father.State = new JoyState();  // S3
                        break;
                    }
            }
        }

        private void Hope()  // y3
        {
            Console.WriteLine("Надеется на хорошие оценки.");
        }
    }

    // Состояние жалости (S1)
    internal class PityState : State
    {
        internal PityState()
        {
            Console.WriteLine("Отец в состоянии жалости:");
            Calm();
        }

        protected override void ChangeState(Father father, Mark mark)
        {
            switch (mark)
            {
                case Mark.Two:
                    {
                        father.State = new AngerState(); // S4
                        break;
                    }
                case Mark.Five:
                    {
                        father.State = new NeutralState(); // S0
                        break;
                    }
            }
        }

        private void Calm()  // y2

        {
            Console.WriteLine("Успокаивает сына.");
        }
    }

    // Состояние сильного гнева (S2)
    internal class StrongAngerState : State
    {
        internal StrongAngerState()
        {
            Console.WriteLine("Отец в состоянии сильного гнева:");
            BeatBelt();
        }

        protected override void ChangeState(Father father, Mark mark)
        {
            switch (mark)
            {
                case Mark.Two:
                    {
                        father.State = new StrongAngerState(); // S2
                        break;
                    }
                case Mark.Five:
                    {
                        father.State = new NeutralState(); // S0
                        break;
                    }
            }
        }
        
        private void BeatBelt()  // y0
        {
            Console.WriteLine("Бьет сына ремнем.");
        }
    }

    // Состояние радости (S3)
    internal class JoyState : State
    {
        internal JoyState()
        {
            Console.WriteLine("Отец в состоянии радости:");
            Joy();
        }

        protected override void ChangeState(Father father, Mark mark)
        {
            switch (mark)
            {
                case Mark.Two:
                    {
                        father.State = new PityState(); // S1
                        break;
                    }

                case Mark.Five:
                    {
                        father.State = new StrongJoyState(); // S5
                        break;
                    }
            }
        }
        
        private void Joy()  // y4
        {
            Console.WriteLine("Радуется успехам сына.");
        }
    }

    // Состояние гнева (S4)
    internal class AngerState : State
    {
        internal AngerState()
        {
            Console.WriteLine("Отец в состоянии гнева:");
            Scold();
        }

        protected override void ChangeState(Father father, Mark mark)
        {
            switch (mark)
            {
                case Mark.Two:
                    {
                        father.State = new StrongAngerState(); // S2
                        break;
                    }
                case Mark.Five:
                    {
                        father.State = new NeutralState(); // S0
                        break;
                    }
            }
        }
        
        private void Scold()  // y1
        {
            Console.WriteLine("Ругает сына.");
        }
    }

    // Состояние сильной радости (S5)
    internal class StrongJoyState : State
    {
        internal StrongJoyState()
        {
            Console.WriteLine("Отец в состоянии сильной радости:");
            Exult();
        }





        protected override void ChangeState(Father father, Mark mark)
        {
            switch (mark)
            {
                case Mark.Two:
                    {
                        father.State = new PityState(); // S1
                        break;
                    }
                case Mark.Five:
                    {
                        father.State = new StrongJoyState(); // S5
                        break;
                    }
            }
        }
        
        private void Exult()  // y5
        {
            Console.WriteLine("Ликует и гордится сыном.");
        }
    }

Применимость паттерна

Паттерн State рекомендуется использовать, когда:

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

Результаты

Паттерн State обладает следующими преимуществами:

  • Локализация поведения, зависящего от состояния.

Паттерн State, собирает в одном месте зависящее от состояния поведение и помещает поведение, ассоциированное с некоторым состоянием в отдельный объект. Соответственно, для добавления нового состояния и функции перехода потребуется создать новый класс ConcreteState.

  • Разделяемые объекты состояния.

Разные контексты Context могут разделять один и тот же объект типа State.

© 2017 ITVDN, все права защищены