状态模式的实现

Implementation of the State Pattern

我必须实现一个状态机,它具有每个状态模式都应具备的非常基本的要求:

  1. 状态机一次可以处于任何一个状态。
  2. 从 X 状态到 Y 状态的转换与从 Y 到 Z 或从任何其他状态到另一个状态的转换具有不同的参数。
  3. 将运行 "state machine" 的用户程序当然不能转换到您处于特定状态时不允许进入的状态。例如如果 stateMachine.currentState() 不是 CASHACCEPTED
  4. stateMachine.dispenseCard() 将不起作用

我尝试关注 this link,但这里:

  1. 抽象状态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);
    }}
    
  2. 为什么状态class "has a"设备会转换到不同的状态?不应该反过来吗?就像门应该 "has a" state.

您给出的示例代码实际上定义了一个 State 具有所有 BehaviorsContext(本例中为门)。 State 定义 Context 在这个状态。

例如,当 DoorDoorOpenedState 中(假设它完全打开)。当方法 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 将在调用 DispenceNoodlesReturnCache 时抛出异常,因为这是此状态下的无效行为。

当用户输入缓存时,

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。如果我们有更多数据,这将特别有效。