Пример кода, который вы дали, на самом деле определяет Состояние , которое имеет все Поведения или Context (Дверь в этом примере). State определяет, как Context должен вести себя в этом состоянии.
Например, когда Door
находится в DoorOpenedState
(предположим, что он полностью открыт).когда вызывается метод Open()
для вызова поведения открытия Door
, это должно привести к ошибке (недопустимый переход), поскольку вы не можете перейти с DoorOpenedState
на DoorOpenedState
Состояниешаблоны могут быть реализованы многими различными способами, и переход между состояниями может быть достигнут различными способами.Если вы не читали книгу GOF , там обсуждаются вопросы перехода и возможные реализации.
Вот пример конечного автомата для торгового автомата.Чтобы упростить пример и сконцентрироваться на конечном автомате и переходах, скажем, что у нашего конечного автомата есть только лапша и он не возвращает лишних денег.Поэтому, если чашка лапши стоит 5 $, а вы даете ей 7 $, она не вернет 2 $.
ПРИМЕЧАНИЕ. Поскольку требуется обмен данными между NoodleVendingMachine
и каждым состоянием. Для простоты яЯ сделаю эти методы внутренними, чтобы просто пометить их.Для реального проекта может потребоваться дополнительный интерфейс, чтобы скрыть их от клиентского кода NoodleVendingMachine
и сохранить их между NoodleVendingMachine
и только его состояниями.
public class CacheStorage {
public Cache AvailableCache { get; private set; }
public void AddCache(Money cache) {
AvailabletCache += cache;
}
public void ClearAvailableCache() {
AvailabletCache = Money.None;
}
}
public interface INoodleVendingMachineState {
void TakeCache(Money money);
Noodles DispenceNoodles();
Money ReturnCache();
}
public class NoodleVendingMachine {
private INoodleVendingMachineState mState;
itnernal CacheStorage CacheStorage { get; private set; }
public NoodlesPrice { get; private set; }
public Money AvailableCache { get { return CacheStorage.AvailableCache; } }
public NoodleVendingMachine() {
NoodlesPrice = new Money(Currency.USD, 5); // 5 bucks for noodles
CacheStorage = new CacheStorage();
mState = new WaitingForCacheState(this);
}
public void TakeCache(Money money) {
mState.TakeCache(money);
}
public Noodles DispenceNoodles() {
return mState.DispenceNoodles();
}
public Money ReturnCache() {
return mState.ReturnCache();
}
internal void TransitionTo(INoodleVendingMachineState state) {
mState = state;
}
}
public WaitingForCacheState : INoodleVendingMachineState {
private NoodlesVendingMachine mVendingMachine;
public WaitingForCacheState(NoodlesVendingMachine vendingMachine) {
mVendingMachine = vendingMachine;
}
public void TakeCache(Money cache) {
mVendingMachine.CacheStorage.AddCache(cache);
mVendingMachine.TransitionTo(new CacheAvailableState(mVendingMachine));
}
public Noodles DispenceNoodles() {
throw new InsertCacheFirstException();
}
public Money ReturnCache() {
throw new CacheNotAvailableException();
}
}
public CacheAvailableState : INoodleVendingMachineState {
private CacheStorage mCacheStorage;
private NoodleVendingMachine mVendingMachine;
public CacheAvailableState(NoodleVendingMachine vendingMachine) {
if (vendingMachine.AvailableCache == Money.None){
throw new CacheNotAvailable()
}
mVendingMachine = vendingMachine;
mCacheStorage = mVendingMachine.CacheStorage;
}
public void TakeCache(Money cache) {
mCacheStorage.AddCache(cache);
}
public Noodles DispenceNoodles() {
if(mCacheStorage.AvailableCache < mVendingMachine.NoodlesPrice) {
throw new CacheNotEnoughtException();
}
mCacheStorage.ClearAvailableCache();
mVendingMachine.TransitionTo(new WaitingForCacheState(mVendingMachine));
return new Noodles();
}
public Money ReturnCache() {
var cache = mCacheStorage.AvailableCache;
mCacheStorage.ClearAvailableCache();
mVendingMachine.TransitionTo(new WaitingForCacheState(mVendingMachine));
return cache;
}
}
Здесь мы фиксируем поведение торгового автомата с состояниями .
WaitingForCacheState
будет выдавать исключения при вызове DispenceNoodles
или ReturnCache
, так как это некорректное поведение в этом состоянии.
WaitingForCacheState
выполнит переход состояния в CacheAvailableState
, когдапользовательский ввод кеша.Когда кэш доступен, мы видим, что все поведения поддерживаются.Когда лапша выдается или пользователь просит вернуть свои деньги, мы делаем переход состояния в WaitingForCacheState
.
В этом примере каждое состояние выполняет переходы состояния в следующее соответствующее состояние.
Если выЕсли у вас есть более сложный пример для вашего конечного автомата, вам, вероятно, потребуется решить, где хранить параметры.Вы можете сохранить его в Context (NoodlesVendingMachine
в нашем случае).В этом примере деньги хранятся в специальном CacheStorage
, так что каждый штат и NoodlesVendingMachine
имеют доступ к ним, и они могут принимать решения на основе их стоимости.когда действие выполняется (например, DispenceNoodles
), текущее состояние проверяет значение CacheStorage
и принимает решение, выполнить ли переход, выполнить какое-либо поведение (TakeMoney
in CacheAvailableState
), сгенерировать ошибку иливыполнить поведение и затем выполнить переход (ReturnCache
в CacheAvailableState
).
Конечно, если необходимо, вы можете хранить временные данные, специфичные для состояния, в каждом State
, чтобы он мог принимать решения на основе этих данных бездругие объекты знают об этом.
В этом примере CacheAvailableState
может хранить AvailableCache
в нем как свойство.Я решил добавить его в другой класс, чтобы показать, что таким образом несколько объектов могут иметь доступ к данным.Конечно, нам нужно показать AvailableCache
пользователю, поэтому NoodlesVendingMachine
также необходим доступ к доступному кешу.
Мы также можем добавить его в NoodlesVendingMachine
, но это добавит методы и свойства в класс и увеличит его размер.Поэтому мы используем принцип единой ответственности и переносим ответственность за хранение кеша на другой класс.Это будет особенно эффективно, если у нас будет больше данных.