В приведенном вами примере кода фактически определяется Состояние, которое имеет все поведения или < em> Контекст (в данном примере дверь). Состояние определяет, как Контекст должен вести себя в этом состоянии.
Например, когда 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
в CacheAvailableState
), выдать ошибку или выполнить поведение, а затем выполнить переход ( ReturnCache
в CacheAvailableState
).
При необходимости, конечно, вы можете хранить данные, относящиеся к временному состоянию, в каждом State
, чтобы он мог принимать решения на основе этих данных без того, чтобы другие объекты знали об этом.
В этом примере CacheAvailableState
может сохранить AvailableCache
в качестве свойства. Я решил добавить его в другой класс, чтобы показать, что таким образом несколько объектов могут иметь доступ к данным. Конечно, нам нужно показать AvailableCache
пользователю, поэтому NoodlesVendingMachine
также нужен доступ к доступному кешу.
Мы также можем добавить его в NoodlesVendingMachine
, но это добавит в класс методы и свойства и увеличит его размер. Поэтому мы используем принцип единой ответственности и перекладываем ответственность за хранение кеша на другой класс. Это будет особенно эффективно, если у нас будет больше данных.
person
expandable
schedule
15.05.2019