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

    Далее пример взят из книги Джимми Нильссона "Применение DDD и шаблонов проектирования".

    Задача

    Заказ на покупку может находиться в одном из следующих состояний: New Order (Новый заказ), Registered (Зарегистрирован), Granted (Подтвержден), Shipped (Отгружен), Invoiced (Выписана счет-фактура) и Cancelled (Отменен). Имеются строгие правила, которые регламентируют переход заказа из одного состояния в другое. Например, не разрешается переход из состояния Registered в состояние Shipped, т.к. перед отгрузкой заказ обязательно проходит стадию регистрации.
    Кроме того, имеются отличия в поведении в зависимости от конкретного состояния. Так из состояния Cancelled нельзя переходить в состояние AddOrderOnLine, чтобы ввести в заказ дополнительные позиции. Тоже самое относится и к состояниям Shipped, Invoiced.
    Также следует иметь ввиду, что определенное поведение приводит к смене состояния. Например, при изменении заказа, находившегося в состоянии Granted мы попадаем обратно в состояние New Order.


    Автор книги приводит 3 возможных решения, далее я коротко расскажу о них.

    1) Использовать enum OrderState, в котором будут перечислены все состояния. В классе заказа используется поле для хранения состояния. Для каждой смены состояния написана своя отдельная функция Register(), Grant() и т.д. Если вы попытаетесь решить задачу этим путем, то обнаружите, что в функциях класса Order будет много избыточного и дублирующегося кода, следящего за логикой переходов.

    2) В классе Order сделать одну функцию ChangeState(OrderState newState). enum также остается. Т.е. одна функция при помощи оператора switch реализует всю логику переходов. И также присутствуют функции Register(), Grant() и т.д., которые вызывают функцию ChangeState с нужным состоянием. Здесь автор сетует на то, что функция ChangeState(), которую он даже не решился полностью включить в книгу, является примером недоброкачественного кода. Особенно если учесть, что в примере задания ничего не сказано про какие-то особенности смены состояний. Если при смене придется делать еще какие-то действия, то вся логика окажется в функции ChangeState(). Вобщем, это не хорошо.

    3) Решение основанное на таблице переходов. Для каждого состояния выписывается возможный переход и новое состояние после такого перехода. Здесь также реализовано через функцию ChangeState(), но она на вход принимает уже не новое состояние, а имя функции перехода или соответствующего ей метода. Автор говорит, что это довольно простое и ясное решение, а мне кажется, что оно мало чем отличается от 2-го. Поверим автору, хотя он это решение также отбрасывает, по той же причине что и 2-е: трудно учесть специальное поведение в зависимости от текущего состояния.

    Что же предлагает нам автор взамен этих 3-х неудачных решений? Верно, шаблон проектирования State.

    Общая структура шаблона State.

                  Шаблон проектирования State (Состояние)
    Структура шаблона State примененная для данной задачи.

                  Шаблон проектирования State (Состояние) реализованный для задачи
   
    Далее я приведу код, который мне пришлось написать самому, для реализации этого шаблона. Сразу скажу, - автор упомянул, что реализовать OrderState можно как абстрактный класс, интерфейс или просто базовый класс. Я попробовал все. Для меня важно было, чтобы при невозможных переходах кидались исключения с сообщением какую операцию нельзя произвести и в каком состоянии мы находились на момент ее выполнения. И эту задачу очень красиво удается решить используя базовый класс! Не интерфейс и не абстрактный класс. А причина следующая: при реализации через базовый класс всю логику исключений можно поместить в него, а в состояниях-наследниках реализовать только те функции, которые отвечают за возможные переходы. Это очень удобно, ошибок допустить при таком решении можно минимум, изменять поведение всей системы достаточно просто. Во всех остальных случаях пришлось бы в каждом классе реализовывать все функции переходов по состояниям и в тех, что запрещены по условию задачи, кидать исключение. Объем кода значительно растет, количество ошибок соответственно тоже. Лично мне для такой мелкой задачи писать такой объем кода стало лень. Кстати, сам автор приводит пример с реализацией через один базовый класс. Если бы исключения кидать не нужно было в случае неверных переходов, а можно было оставлять их функции пустыми, то вполне сгодился бы вариант с интерфейсом или абстрактным классом. Но лично мне вариант с исключениями нравится больше, т.к. исключения мне гарантируют, что логика, заложенная в схему переходов, низачто не будет нарушена. А если и будет, то мы получим исключение. И на этапе тестирования приложения такое исключение должно быть отловлено, соответственно баги в логике будут исправлены. И этого намного лучше, чем отдавать приложение заказчику, в котором будут присутствовать баги в логике. Все вышесказанное относится к данной задаче. Возможно, в других задачах исключения не потребуются. Итак, посмотрим на код.

    Ниже приведен код, полностью описывающий задачу шаблоном State.
	// Класс Order, описывающий заказ. Содержит в себе поле, хранящее состояние заказа.
	class Order
	{
		internal OrderState _currentState = null; // Состояние заказа

		public Order()
		{
			_currentState = new NewOrder(this);
		}

		public void AddOrderOnLine()
		{
			_currentState.AddOrderOnLine();
		}

		public void Register()
		{
			_currentState.Register();
		}

		public void Grant()
		{
			_currentState.Grant();
		}

		public void Ship()
		{
			_currentState.Ship();
		}

		public void Invoice()
		{
			_currentState.Invoice();
		}

		public void Cancel()
		{
			_currentState.Cancel();
		}
	}

	// Базовый класс OrderState для всех состояний
	class OrderState
	{
		protected Order _parent; // Состояние относится к заказу

		public virtual string State { get; set; } // Имя состояния

		public virtual void AddOrderOnLine()
		{
			throw new WrongChangeOrderStateException("AddOrderOnLine", _parent._currentState.State);
		}

		public virtual void Register()
		{
			throw new WrongChangeOrderStateException("Register", _parent._currentState.State);
		}

		public virtual void Grant()
		{
			throw new WrongChangeOrderStateException("Grant", _parent._currentState.State);
		}

		public virtual void Ship()
		{
			throw new WrongChangeOrderStateException("Ship", _parent._currentState.State);
		}

		public virtual void Invoice()
		{
			throw new WrongChangeOrderStateException("Invoice", _parent._currentState.State);
		}

		public virtual void Cancel()
		{
			throw new WrongChangeOrderStateException("Cancel", _parent._currentState.State);
		}
	}

	// В классе определенного состояния реализуются только те функции-переходы, 
	//  которые ведут к возможным состояниям.
	// Ниже идет описание классов состояний по порядку. 
	class NewOrder : OrderState
	{
		public NewOrder(Order order)
		{
			_parent = order;
		}

		public override string State { get { return "NewOrder"; } }

		public override void Register()
		{
			_parent._currentState = new Registered(_parent);
		}

		public override void Cancel()
		{
			_parent._currentState = new Cancelled(_parent);
		}
	}

	// Состояние Registered
	class Registered : OrderState
	{
		public Registered(Order order)
		{
			_parent = order;
		}

		public override string State { get { return "Registered"; } }

		public override void AddOrderOnLine()
		{
			_parent._currentState = new NewOrder(_parent);
		}

		public override void Grant()
		{
			_parent._currentState = new Granted(_parent);
		}

		public override void Cancel()
		{
			_parent._currentState = new Cancelled(_parent);
		}
	}

	// Состояние Granted
	class Granted : OrderState
	{
		public Granted(Order order)
		{
			_parent = order;
		}

		public override string State { get { return "Granted"; } }

		public override void AddOrderOnLine()
		{
			_parent._currentState = new NewOrder(_parent);
		}

		public override void Ship()
		{
			_parent._currentState = new Shipped(_parent);
		}

		public override void Cancel()
		{
			_parent._currentState = new Cancelled(_parent);
		}
	}

	// Состояние Shipped
	class Shipped : OrderState
	{
		public Shipped(Order order)
		{
			_parent = order;
		}

		public override string State { get { return "Shipped"; } }

		public override void Invoice()
		{
			_parent._currentState = new Invoiced(_parent);
		}
	}

	// Состояние Invoiced
	class Invoiced : OrderState
	{
		public Invoiced(Order order)
		{
			_parent = order;
		}

		public override string State { get { return "Invoiced"; } }
	}

	// Состояние Cancelled
	class Cancelled : OrderState
	{
		public Cancelled(Order order)
		{
			_parent = order;
		}

		public override string State { get { return "Cancelled"; } }
	}

	// Класс исключения сообщает о невозможном совершении операции operationName
	//  из определенного состояния stateName
	class WrongChangeOrderStateException: Exception
	{
		public WrongChangeOrderStateException(string operationName, string stateName)
			: base(string.Format("Unable to do {0} operation from state {1}", operationName, stateName))
		{
		}
	}
    Последнее замечание для тех, кто все же собрался использовать этот паттерн проектирования. Замечание будет насчет того, куда поместить дополнительную логику, которая будет выполняться при смене состояний. Ниже будет приведен код, который предлагает автор книги. Смысл в том, чтобы делать обратные вызовы родителя. Заметьте, что обратные функции содержат символ "_" в имени. Это говорит о том, что это не те же самые функции, что делают смену состояний, а те, что выполняют дополнительную логику по работе, связанной со сменой состояния. Здесь автор приводит слова, которые я не совсем понял: "обратный вызов передается внутреннему методу _Register(), а не общедоступному методу Register()". Но данный метод должен иметь модификатор public, что автоматически его делает общедоступным! Возможно автор или переводчик ошиблись, или же он вкладывал в эти слова смысл, который я не смог понять. Но это не важно, суть приницпа понятна. Тем более, что помимо этого способа, автор предлагает еще два: поместить код, в базовый класс OrderState или же в класс состояния NewOrder. Главное - это выбрать наиболее подходящее место.
	class NewOrder : OrderState
	{
		//...

		public override void Register()
		{
			_parent._Register(); // Обратный вызов родителя (функция должна быть реализована в классе Order)
			_parent._currentState = new Registered(_parent);
		}

		//...
	}
Помогло?
36