Menu
Coddy logo textTech

Паттерн Состояние

Часть раздела Объектно-ориентированное программирование путешествия по 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, которые проверяют текущее состояние. Каждый класс состояния обрабатывает только своё собственное поведение, что делает код более простым в поддержке и расширении новыми состояниями.

challenge icon

Задание

Легко

Давайте создадим систему светофора, используя паттерн Состояние (State). Светофоры последовательно переключаются между различными состояниями (красный, желтый, зеленый), и каждое состояние определяет, что происходит при смене сигнала. Вместо использования сложных условных операторов, вы инкапсулируете каждое состояние светофора в отдельный класс.

Вы организуете свой код в трех файлах:

  • TrafficLightState.cs: Определите интерфейс ITrafficLightState в пространстве имен Traffic с методом Change(TrafficLight light). Затем создайте три конкретных класса состояний:
    • RedState — при вызове Change выводит Red - Stop! Changing to Green... и переводит светофор в GreenState
    • GreenState — выводит Green - Go! Changing to Yellow... и переводит в YellowState
    • YellowState — выводит 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 раз, используя цикл
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование