【问题标题】:Implementation of the State Pattern状态模式的实现
【发布时间】:2020-03-23 11:32:25
【问题描述】:

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

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

我尝试关注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 类“具有”转换到不同状态的设备?不应该反过来吗?就像门应该“有一个”状态。

【问题讨论】:

    标签: design-patterns state-pattern


    【解决方案1】:

    您给出的示例代码实际上定义了一个 State,它具有所有 Behaviors上下文(本例中为门)。 State 定义了 Context 在此状态下的行为方式。

    例如,当DoorDoorOpenedState 中时(假设它完全打开)。当调用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的值并决定是否进行转换、执行某些行为(TakeMoney in CacheAvailableState)、抛出错误或执行行为,然后进行转换(CacheAvailableState 中的ReturnCache)。

    当然,如有必要,您可以在每个 State 中存储临时状态特定数据,以便它可以根据该数据做出决策,而其他对象不知道。

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

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

    【讨论】:

    • 你写的这么详细这么复杂,看不懂
    • 状态模式是一个棘手的模式。这就是为什么我给出了详细的例子。我会试着总结一下。抽象的 State 类定义了 Context 的所有可能的行为(不是状态)(文章中的 Door,我的示例中的 VendingMachine)。每个 State 对象表示它的 Context(Door、VendingMachine、StateMachine 等)在此状态下应该如何表现。他们可能需要共享数据(在我的示例中为 CacheStorage,因为他们都需要访问客户端提供并用于状态转换的资金,即您示例中状态转换所需的参数)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-06
    • 2010-09-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多