Паттерн Состояние
Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 60 из 70.
Паттерн Состояние — это поведенческий паттерн, который позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Вместо использования сложных условных операторов для обработки различных состояний, вы инкапсулируете каждое состояние в отдельный класс и делегируете поведение текущему объекту состояния.
Паттерн состоит из трех компонентов: интерфейса состояния, определяющего поведение, специфичное для конкретного состояния, конкретных классов состояний, реализующих это поведение, и класса контекста, который хранит ссылку на текущее состояние:
public interface IDocumentState
{
void Publish(Document doc);
}
public class DraftState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Moving to moderation");
doc.SetState(new ModerationState());
}
}
public class ModerationState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Publishing document");
doc.SetState(new PublishedState());
}
}
public class PublishedState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Already published");
}
}Контекст хранит текущее состояние и делегирует ему действия. Объекты состояний могут инициировать переходы, вызывая SetState() в контексте:
public class Document
{
private IDocumentState _state = new DraftState();
public void SetState(IDocumentState state) => _state = state;
public void Publish() => _state.Publish(this);
}
var doc = new Document();
doc.Publish(); // Переход к модерации
doc.Publish(); // Публикация документа
doc.Publish(); // Уже опубликованоПаттерн Состояние устраняет разрастающиеся инструкции if-else или switch, которые проверяют текущее состояние. Каждый класс состояния обрабатывает только своё собственное поведение, что делает код более простым в поддержке и расширении новыми состояниями.
Задание
ЛегкоДавайте создадим систему светофора, используя паттерн Состояние (State). Светофоры последовательно переключаются между различными состояниями (красный, желтый, зеленый), и каждое состояние определяет, что происходит при смене сигнала. Вместо использования сложных условных операторов, вы инкапсулируете каждое состояние светофора в отдельный класс.
Вы организуете свой код в трех файлах:
TrafficLightState.cs: Определите интерфейсITrafficLightStateв пространстве именTrafficс методомChange(TrafficLight light). Затем создайте три конкретных класса состояний:RedState— при вызовеChangeвыводитRed - Stop! Changing to Green...и переводит светофор вGreenStateGreenState— выводитGreen - Go! Changing to Yellow...и переводит вYellowStateYellowState— выводитYellow - Caution! Changing to Red...и переводит вRedState
TrafficLight.cs: Создайте классTrafficLightв том же пространстве имен. Это ваш класс контекста, который хранит текущее состояние. По умолчанию он должен начинаться сRedStateи иметь:- Метод
SetState(ITrafficLightState state), который объекты состояний используют для запуска переходов - Метод
Change(), который делегирует выполнение методуChangeтекущего состояния
- Метод
Program.cs: Объедините все вместе, создав объектTrafficLightи циклически переключая состояния на основе входных данных.
Вы получите одни входные данные:
- Количество переключений светофора (целое число)
Создайте TrafficLight (который начинается с красного) и вызовите Change() указанное количество раз.
Например, если на вход подается 3, вывод должен быть следующим:
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...
Yellow - Caution! Changing to Red...Если на вход подается 5, светофор проходит цикл повторно:
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...
Yellow - Caution! Changing to Red...
Red - Stop! Changing to Green...
Green - Go! Changing to Yellow...Обратите внимание, что класс TrafficLight не содержит логики о том, что означает каждый цвет или какой цвет идет следующим — все эти знания находятся в отдельных классах состояний. Каждое состояние отвечает за свое поведение и за инициирование перехода в следующее состояние!
Шпаргалка
Паттерн Состояние (State) — это поведенческий паттерн, который позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Вместо использования сложных условных операторов вы инкапсулируете каждое состояние в отдельный класс и делегируете поведение текущему объекту состояния.
Паттерн состоит из трех компонентов:
- Интерфейс состояния (State interface): определяет поведение, специфичное для состояния
- Классы конкретных состояний (Concrete state classes): реализуют поведение для каждого состояния
- Класс контекста (Context class): хранит ссылку на текущее состояние
Пример реализации:
// Интерфейс состояния
public interface IDocumentState
{
void Publish(Document doc);
}
// Классы конкретных состояний
public class DraftState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Moving to moderation");
doc.SetState(new ModerationState());
}
}
public class ModerationState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Publishing document");
doc.SetState(new PublishedState());
}
}
public class PublishedState : IDocumentState
{
public void Publish(Document doc)
{
Console.WriteLine("Already published");
}
}
// Класс контекста
public class Document
{
private IDocumentState _state = new DraftState();
public void SetState(IDocumentState state) => _state = state;
public void Publish() => _state.Publish(this);
}
// Использование
var doc = new Document();
doc.Publish(); // Moving to moderation
doc.Publish(); // Publishing document
doc.Publish(); // Already publishedКонтекст хранит текущее состояние и делегирует ему действия. Объекты состояний могут инициировать переходы, вызывая SetState() в контексте. Это избавляет от разрастающихся операторов if-else или switch, делая код более простым в поддержке и расширении новыми состояниями.
Попробуйте сами
using System;
using Traffic;
class Program
{
public static void Main(String[] args)
{
// Считать количество раз, которое нужно сменить сигнал светофора
int n = Convert.ToInt32(Console.ReadLine());
// TODO: Создать экземпляр TrafficLight (по умолчанию начинается с RedState)
// TODO: Вызвать метод Change() n раз, используя цикл
}
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыПространства имен и директивыВведение в классы и объектыКлючевое слово 'this'Методы и параметрыПоля и свойстваКонструкторыИнициализаторы объектовИтоги — Простой калькулятор4Наследование
Основы синтаксиса наследования (:)Ключевое слово 'base'Ключевые слова Virtual и OverrideЗапечатанные классыБазовый класс 'object'Итоги — Иерархия сотрудников7Продвинутые возможности
Перегрузка операторовИндексаторы (this[])Переопределение ToString()Методы расширенияПовторение — Пользовательский список10Паттерны проектирования. Часть 1
Введение в паттерны проектированияПотокобезопасный SingletonПаттерн FactoryПаттерн Observer (События)Паттерн Strategy13Финальные испытания
Платформа для онлайн-обученияКомпонент игрового персонажаСервис аренды транспорта2Свойства и статические члены
Автоматически реализуемые свойстваСвойства только для чтения и записиСтатические поля и методыСтатические классыЧлены в виде выражений5Полиморфизм и интерфейсы
Полиморфизм: компиляция vs выполнениеИнтерфейс vs абстрактный классМножественные интерфейсыЯвные интерфейсыUpcasting и DowncastingИтоги: Калькулятор фигур8Продвинутые концепции ООП
Композиция вместо наследованияGenerics (классы и методы)Делегаты и событияАтрибуты и рефлексияIDisposable и оператор usingОсновы Dependency Injection11Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн Компоновщик3Архитектура классов
Экземплярные и статические данныеКлючевые слова 'readonly' и 'const'Опорные поляПовторение — Менеджер банковских счетов6Инкапсуляция
Модификаторы доступаСвойства для инкапсуляцииРеализация скрытия данныхПаттерны неизменяемостиИтоги — Записи студентов9Переменное количество аргументов
Ключевое слово 'params'Необязательные параметрыИменованные аргументыПерегрузка методов