您给出的示例代码实际上定义了一个 State,它具有所有 Behaviors 或 上下文(本例中为门)。 State 定义了 Context 在此状态下的行为方式。
例如,当Door 在DoorOpenedState 中时(假设它完全打开)。当调用Open() 方法来调用Door 的行为打开时,应该会导致错误(无效转换),因为您无法从DoorOpenedState 转换到DoorOpenedState
状态模式可以用许多不同的方式实现,状态之间的转换可以用不同的方式实现。如果您还没有阅读 GOF book,他们会在那里讨论过渡问题和可能的实现。
这是自动售货机的状态机示例。为了简化示例并专注于状态机和转换,假设我们的状态机只有面条并且它不返回多余的钱。所以如果一杯面条是 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)、抛出错误或执行行为,然后进行转换(CacheAvailableState 中的ReturnCache)。
当然,如有必要,您可以在每个 State 中存储临时状态特定数据,以便它可以根据该数据做出决策,而其他对象不知道。
在此示例中,CacheAvailableState 可以将 AvailableCache 作为属性存储在其中。我决定将它添加到另一个类中,以表明这样多个对象可以访问数据。当然我们需要向用户显示AvailableCache,所以NoodlesVendingMachine 也需要访问可用的缓存。
我们也可以将它添加到NoodlesVendingMachine,但这会为类添加方法和属性并增加它的大小。所以我们使用Single-Responsibility principle 并将存储缓存的责任转移到另一个类。如果我们有更多数据,这将特别有效。