状态模式的实现
Implementation of the State Pattern
我必须实现一个状态机,它具有每个状态模式都应具备的非常基本的要求:
- 状态机一次可以处于任何一个状态。
- 从 X 状态到 Y 状态的转换与从 Y 到 Z 或从任何其他状态到另一个状态的转换具有不同的参数。
- 将运行 "state machine" 的用户程序当然不能转换到您处于特定状态时不允许进入的状态。例如如果
stateMachine.currentState()
不是 CASHACCEPTED
,stateMachine.dispenseCard()
将不起作用
我尝试关注 this link,但这里:
抽象状态class需要定义状态机所有可能的状态,具体状态需要实现所有的状态方法。为什么具体状态 class 应该对所有转移到其他状态的其他方法感兴趣?为什么不仅是这个状态过渡到的那些?
public abstract class DoorState : DomainObject {
protected Door _door;
public Door Door
{
get { return _door; }
set { _door = value; }
}
public abstract void Close();
public abstract void Open();
public abstract void Break();
public abstract void Lock();
public abstract void Unlock();
/// <summary>
/// Fix simulates a repair to the Door and resets
/// the initial state of the door to closed.
/// </summary>
public void Fix()
{
_door.DoorState = new DoorClosedState(this);
}}
为什么状态class "has a"设备会转换到不同的状态?不应该反过来吗?就像门应该 "has a" state.
您给出的示例代码实际上定义了一个 State 具有所有 Behaviors 或 Context(本例中为门)。 State 定义 Context 在这个状态。
例如,当 Door
在 DoorOpenedState
中(假设它完全打开)。当方法 Open()
被调用以调用 Door
的行为打开时,则应导致错误(无效转换),因为您无法从 DoorOpenedState
转换到 DoorOpenedState
状态模式可以用许多不同的方式实现,状态之间的转换可以用不同的方式实现。如果您还没有阅读 GOF book,他们会在那里讨论过渡问题和可能的实施。
这是自动售货机的状态机示例。为了简化示例并专注于状态机和转换,假设我们的状态机只有面条并且没有 return 多余的钱。所以如果一杯面条是 5 美元,你给它 7 美元,它不会 return 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
in CacheAvailableState
).
当然,如果有必要,您可以在每个 State
中存储临时状态特定数据,以便它可以根据该数据做出决策,而其他对象不知道。
在此示例中,CacheAvailableState
可以将 AvailableCache
作为 属性 存储在其中。我决定将它添加到另一个 class 以表明通过这种方式多个对象可以访问数据。当然,我们需要向用户显示 AvailableCache
,因此 NoodlesVendingMachine
也需要访问可用缓存。
我们也可以将它添加到 NoodlesVendingMachine
,但这会向 class 添加方法和属性并增加它的大小。所以我们使用 Single-Responsibility principle 并将存储缓存的责任转移到另一个 class。如果我们有更多数据,这将特别有效。
我必须实现一个状态机,它具有每个状态模式都应具备的非常基本的要求:
- 状态机一次可以处于任何一个状态。
- 从 X 状态到 Y 状态的转换与从 Y 到 Z 或从任何其他状态到另一个状态的转换具有不同的参数。
- 将运行 "state machine" 的用户程序当然不能转换到您处于特定状态时不允许进入的状态。例如如果
stateMachine.currentState()
不是CASHACCEPTED
,
stateMachine.dispenseCard()
将不起作用
我尝试关注 this link,但这里:
抽象状态class需要定义状态机所有可能的状态,具体状态需要实现所有的状态方法。为什么具体状态 class 应该对所有转移到其他状态的其他方法感兴趣?为什么不仅是这个状态过渡到的那些?
public abstract class DoorState : DomainObject { protected Door _door; public Door Door { get { return _door; } set { _door = value; } } public abstract void Close(); public abstract void Open(); public abstract void Break(); public abstract void Lock(); public abstract void Unlock(); /// <summary> /// Fix simulates a repair to the Door and resets /// the initial state of the door to closed. /// </summary> public void Fix() { _door.DoorState = new DoorClosedState(this); }}
为什么状态class "has a"设备会转换到不同的状态?不应该反过来吗?就像门应该 "has a" state.
您给出的示例代码实际上定义了一个 State 具有所有 Behaviors 或 Context(本例中为门)。 State 定义 Context 在这个状态。
例如,当 Door
在 DoorOpenedState
中(假设它完全打开)。当方法 Open()
被调用以调用 Door
的行为打开时,则应导致错误(无效转换),因为您无法从 DoorOpenedState
转换到 DoorOpenedState
状态模式可以用许多不同的方式实现,状态之间的转换可以用不同的方式实现。如果您还没有阅读 GOF book,他们会在那里讨论过渡问题和可能的实施。
这是自动售货机的状态机示例。为了简化示例并专注于状态机和转换,假设我们的状态机只有面条并且没有 return 多余的钱。所以如果一杯面条是 5 美元,你给它 7 美元,它不会 return 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
in CacheAvailableState
).
当然,如果有必要,您可以在每个 State
中存储临时状态特定数据,以便它可以根据该数据做出决策,而其他对象不知道。
在此示例中,CacheAvailableState
可以将 AvailableCache
作为 属性 存储在其中。我决定将它添加到另一个 class 以表明通过这种方式多个对象可以访问数据。当然,我们需要向用户显示 AvailableCache
,因此 NoodlesVendingMachine
也需要访问可用缓存。
我们也可以将它添加到 NoodlesVendingMachine
,但这会向 class 添加方法和属性并增加它的大小。所以我们使用 Single-Responsibility principle 并将存储缓存的责任转移到另一个 class。如果我们有更多数据,这将特别有效。