执行状态模式

时间:2019-05-15 10:32:41

标签: design-patterns state

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

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

我尝试点击this链接,但在这里:

  1. 抽象State类需要定义状态机的所有可能状态,因此具体状态需要实现所有状态方法。为什么具体的状态类对转换到其他状态的所有其他方法都感兴趣?为什么不仅要转变为这种状态?

    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. 为什么State类“具有一个”转换为不同状态的Device?不应该这样吗?就像门应该“处于”状态。

1 个答案:

答案 0 :(得分:0)

您提供的示例代码实际上定义了一个 状态 ,其中包含所有 行为 或< strong> 上下文 (在此示例中为门)。 状态 定义了在这种状态下 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将在调用DispenceNoodlesReturnCache时引发异常,因为在此状态下这是无效行为。

用户输入高速缓存时,

WaitingForCacheState会进行到CacheAvailableState的状态转换。当缓存可用时,我们看到支持所有行为。分发面条或用户要求退款时,我们将状态转换为WaitingForCacheState

在此示例中,每个状态都将状态转换为下一个适当的状态。

如果您的状态机示例更为复杂,则可能需要确定将参数存储在何处。您可以将其存储在 Context 中(在我们的情况下为NoodlesVendingMachine)。在此示例中,货币存储在特殊的CacheStorage中,因此每个州和NoodlesVendingMachine都可以访问它们,并可以根据其值进行决策。当执行操作时(例如DispenceNoodles),当前状态会检查CacheStorage的值,并决定是否进行过渡,执行某些行为({{ 1}}),抛出错误或执行行为,然后进行转换(TakeMoney中的CacheAvailableState)。

当然,如有必要,您可以在每个ReturnCache中存储临时状态特定的数据,这样它就可以基于该数据做出决策,而其他对象则不知道。

在此示例中,CacheAvailableState可以将State作为属性存储在其中。我决定将其添加到另一个类中,以表明通过这种方式,多个对象可以访问数据。当然,我们需要向用户显示CacheAvailableState,因此AvailableCache也需要访问可用的缓存。

我们也可以将其添加到AvailableCache中,但这将为类添加方法和属性并增加其大小。因此,我们使用Single-Responsibility principle并将存储缓存的职责移到另一个类。如果我们有更多数据,这将特别有效。