Паттерны проектирования: Состояние (State)

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

Простейшая схема работы паттерна:

Данный паттерн состоит из 3 блоков:

  • Класс, который будет менять своё поведение в зависимости от состояния.
  • Интерфейс, который должен реализовать каждое из конкретных состояний.
  • Классы конкретных состояний.

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

Интерфейс IState будет иметь следующий вид.

 
    interface IState
    {
        void On();
        void Off();
        void Print();
        void AddPaper(int count);
    }
Состояние отключённого питания:
 
    class PowerOffState : IState
    {
        private readonly Printer _printer;

        public PowerOffState(Printer printer)
        {
            _printer = printer;
        }
        public void On()
        {
            Console.WriteLine("Принтер включен");
            _printer.SetState(_printer.WaitingState);
        }

        public void Off()
        {
            Console.WriteLine("Принтер и так выключен");
        }

        public void Print()
        {
            Console.WriteLine("Принтер отключен, печать невозможна");
        }

        public void AddPaper(int count)
        {
            _printer.AddPaper(count);
            Console.WriteLine("Бумага добавлена");
        }
    }
Состояние ожидания печати:
 
    class WaitingState : IState
    {
        private readonly Printer _printer;

        public WaitingState(Printer printer)
        {
            _printer = printer;
        }

        public void On()
        {
            Console.WriteLine("Принтер уже и так включен");
        }

        public void Off()
        {
            Console.WriteLine("Принтер выключен");
        }

        public void Print()
        {
            if (_printer.CountPaper > 0)
            {
                Console.WriteLine("Сейчас всё распечатаем");
                _printer.AddPaper(-1);
            }
            else
            {
                _printer.SetState(_printer.PaperOffState);
                _printer.PrintDocument();
            }
        }

        public void AddPaper(int count)
        {
            _printer.AddPaper(count);
            Console.WriteLine("Бумага добавлена");
        }
    }
Состояние отсутствия бумаги:
 
    class PaperOffState : IState
    {
        private readonly Printer _printer;

        public PaperOffState(Printer printer)
        {
            _printer = printer;
        }

        public void On()
        {
            Console.WriteLine("Принтер уже и так включен");
        }

        public void Off()
        {
            Console.WriteLine("Принтер выключен");
            _printer.SetState(_printer.PowerOffState);
        }

        public void Print()
        {
            if (_printer.CountPaper > 0)
            {
                _printer.SetState(_printer.PrintState);
                _printer.PrintDocument();
            }
            else
            {
                Console.WriteLine("Бумаги нет, печатать не буду");
            }

        }

        public void AddPaper(int count)
        {
            Console.WriteLine("Добавляем бумагу");
            _printer.AddPaper(count);
            if (_printer.CountPaper > 0)
                _printer.SetState(_printer.WaitingState);
        }
    }
Состояние печати:
 
    class PrintState : IState
    {
        private readonly Printer _printer;

        public PrintState(Printer printer)
        {
            _printer = printer;
        }
        public void On()
        {
            Console.WriteLine("Принтер уже и так включен");
        }

        public void Off()
        {
            Console.WriteLine("Принтер выключен");
        }

        public void Print()
        {
            if (_printer.CountPaper > 0)
            {
                Console.WriteLine("Идёт печать...");
                _printer.AddPaper(-1);
                _printer.SetState(_printer.WaitingState);
            }

            else
            {
                _printer.SetState(_printer.PaperOffState);
                _printer.PrintDocument();
            }

        }

        public void AddPaper(int count)
        {
            _printer.AddPaper(count);
            Console.WriteLine("Бумага добавлена");
        }
    }
Осталось реализовать сам класс принтера. Он будет выглядеть следующим образом:
 
    class Printer
    {
        private IState _state;
        private int _countPaper;

        public PaperOffState PaperOffState { get; private set; }
        public PowerOffState PowerOffState { get; private set; }
        public PrintState PrintState { get; private set; }
        public WaitingState WaitingState { get; private set; }
        public int CountPaper {
            get { return _countPaper; }
        }

        public Printer()
        {
            PowerOffState = new PowerOffState(this);
            PaperOffState = new PaperOffState(this);
            PrintState = new PrintState(this);
            WaitingState = new WaitingState(this);
            _state = WaitingState;
        }

        public void SetState(IState state)
        {
            _state = state;
        }

        public void PrintDocument()
        {
            _state.Print();
        }

        public void PowerOff()
        {
            _state.Off();
        }
        public void PowerOn()
        {
            _state.On();
        }

        public void AddPaper(int count)
        {
            _countPaper += count;
        }
    }
Теперь выполним проверку:
 
var printer = new Printer();
printer.PowerOn();
printer.PrintDocument();
printer.AddPaper(3);
printer.PrintDocument();
printer.PrintDocument();
printer.PrintDocument();
printer.PrintDocument();
printer.PowerOff();

Полученный результат:

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

Исходный код учебного проекта: http://1drv.ms/11X5ykA

Комментарии (3) -

Владимир Ческидов 05.03.2017 9:01:21

Добрый день!
Хороший пример шаблона. У вас досадная опечатка в коде в наименовании метода AddPater класса Printer

Андрей 05.03.2017 9:33:56

Добрый!
Спасибо за ваше замечание. Исправил это.

Дмитрий 18.05.2017 17:45:01

Здравствуйте, не получается загрузить не один из ваших файлов, так как ссылки не используются.

Добавить комментарий