65.9K
CodeProject 正在变化。 阅读更多。
Home

理解和实现 C# 中的状态模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (23投票s)

2012年11月5日

CPOL

4分钟阅读

viewsIcon

131696

downloadIcon

1762

本文介绍了什么是状态模式,什么时候可以发现状态模式有用,以及如何在 C# 中实现状态模式的基本实现。

引言

本文介绍了什么是状态模式,什么时候可以发现状态模式有用,以及如何在 C# 中实现状态模式的基本实现。

背景

有时我们会发现应用程序中的某些场景需要维护子系统的状态。此状态需要根据一些条件和/或用户操作进行更改。跟踪这些状态的一种方法是在代码中使用条件逻辑。

使用条件逻辑会得到我们想要的结果,但这会导致代码的可理解性降低,并且更难维护。此外,如果我们需要在系统中添加更多状态,这将非常困难,并且可能在现有系统中造成问题。

为了有效地解决这些问题,GoF 建议使用状态模式。GoF 将状态模式定义为“允许一个对象在其内部状态改变时改变它的行为。该对象看起来好像修改了它的类。


它也可以被认为是一个面向对象的状态机。让我们看看这个模式中的主要类。

  • Context:这是客户端访问的实际对象。需要跟踪此对象的状态。
  • State:这是一个接口或抽象类,它定义了与 Context 的所有可能状态相关的公共行为。
  • ConcreteState:这个类代表 Context 的一个状态。每个状态将表示为一个具体的类。

使用代码

让我们尝试实现一个小例子来详细了解状态模式。让我们尝试为一个虚拟 ATM 机实现软件。ATM 机在我们这里,银行已决定为此机器设置 3 个状态。

  1. 机器中没有卡
  2. 已插入卡并已验证
  3. 现金已提取

现在,首先让我们看看如何在不使用状态模式的情况下实现此解决方案。我们将为 ATM 创建一个类,然后本地维护 ATM 的状态。以下代码段显示了我们如何实现这样的 ATM 类并将机器的状态保存在对象本身中。

class NoStateATM
{
    public enum MACHINE_STATE
    {
        NO_CARD,
        CARD_VALIDATED,        
        CASH_WITHDRAWN,
    }

    private MACHINE_STATE currentState = MACHINE_STATE.NO_CARD;
    private int dummyCashPresent = 1000;

    public string GetNextScreen()
    {
        switch (currentState)
        {
            case MACHINE_STATE.NO_CARD:
                // Here we will get the pin validated
                return GetPinValidated();
                break;
            case MACHINE_STATE.CARD_VALIDATED:
                // Lets try to withdraw the money
                return WithdrawMoney();
                break;
            case MACHINE_STATE.CASH_WITHDRAWN:
                // Lets let the user go now
                return SayGoodBye();
                break;            
        }
        return string.Empty;
    }

    private string GetPinValidated()
    {
        Console.WriteLine("Please Enter your Pin");
        string userInput = Console.ReadLine();

        // lets check with the dummy pin
        if (userInput.Trim() == "1234")
        {
            currentState = MACHINE_STATE.CARD_VALIDATED;
            return "Enter the Amount to Withdraw";
        }
            
        // Show only message and no change in state
        return "Invalid PIN";
    }

    private string WithdrawMoney()
    {            
        string userInput = Console.ReadLine();

        int requestAmount;
        bool result = Int32.TryParse(userInput, out requestAmount);

        if (result == true)
        {
            if (dummyCashPresent < requestAmount)
            {
                // Show only message and no change in state
                return "Amount not present";
            }

            dummyCashPresent -= requestAmount;
            currentState = MACHINE_STATE.CASH_WITHDRAWN;

            return string.Format(@"Amount of {0} has been withdrawn. Press Enter to proceed", requestAmount);
        }

        // Show only message and no change in state
        return "Invalid Amount";
    }

    private string SayGoodBye()
    {
        currentState = MACHINE_STATE.NO_CARD;
        return string.Format("Thanks you for using us, Amount left in ATM: {0}", dummyCashPresent.ToString());
    }
}

现在,当我们运行该类时,我们可以看到它从功能角度来看运行良好。


当我们需要添加更多状态时,问题就会出现。每当我们需要向这台 ATM 机添加更多状态时,我们需要打开 ATM 机代码,然后在该类本身中添加更多逻辑,这明显违反了开闭原则。此外,由于我们以条件逻辑的形式管理所有状态,因此有一些可能性,即更改代码以添加新状态也会破坏现有功能,即这种方式更容易出错。

那么,更好的方法是什么?更好的方法是使用状态模式实现相同的解决方案。我们唯一需要做的是“封装变化”。因此,我们将继续从 ATM 类中提取与状态相关的逻辑,并为每个状态创建单独的类。

让我们首先修改 ATM 类,使其不包含与状态相关的信息。它只会保留一个指向跟踪其状态的对象的句柄。 

public class ATM
{
    public ATMState currentState = null;        

    public ATM()
    {
        currentState = new NoCardState(1000, this);
    }

    public void StartTheATM()
    {
        while (true)
        {
            Console.WriteLine(currentState.GetNextScreen());
        }
    }
}

现在,此类中用作状态句柄的成员变量只是一个抽象类。此类将指向状态的实际具体实现,即机器的真实状态(动态)。

public abstract class ATMState
{
    private ATM atm;

    public ATM Atm
    {
        get { return atm; }
        set { atm = value; }
    }
    private int dummyCashPresent = 1000;

    public int DummyCashPresent
    {
        get { return dummyCashPresent; }
        set { dummyCashPresent = value; }
    }

    public abstract string GetNextScreen();
}

最后,我们有所有具体的类,每个类对应 ATM 机的每个状态。现在让我们看看所有的状态类

NO_CARD 状态的类

class NoCardState : ATMState
{
    // This constructor will create new state taking values from old state
    public NoCardState(ATMState state)     
        :this(state.DummyCashPresent, state.Atm)
    {
        
    }

    // this constructor will be used by the other one
    public NoCardState(int amountRemaining, ATM atmBeingUsed)
    {
        this.Atm = atmBeingUsed;
        this.DummyCashPresent = amountRemaining;
    }

    public override string GetNextScreen()
    {            
        Console.WriteLine("Please Enter your Pin");
        string userInput = Console.ReadLine();

        // lets check with the dummy pin
        if (userInput.Trim() == "1234")
        {
            UpdateState();
            return "Enter the Amount to Withdraw";
        }

        // Show only message and no change in state
        return "Invalid PIN";
    }

    private void UpdateState()
    {
        Atm.currentState = new CardValidatedState(this);
    }
}

CARD_VALIDATED 状态的类

class CardValidatedState : ATMState
{
    // This constructor will create new state taking values from old state
    public CardValidatedState(ATMState state)    
        :this(state.DummyCashPresent, state.Atm)
    {
        
    }

    // this constructor will be used by the other one
    public CardValidatedState(int amountRemaining, ATM atmBeingUsed)
    {
        this.Atm = atmBeingUsed;
        this.DummyCashPresent = amountRemaining;
    }

    public override string GetNextScreen()
    {
        string userInput = Console.ReadLine();

        int requestAmount;
        bool result = Int32.TryParse(userInput, out requestAmount);

        if (result == true)
        {
            if (this.DummyCashPresent < requestAmount)
            {
                // Show only message and no change in state
                return "Amount not present";
            }

            this.DummyCashPresent -= requestAmount;
            UpdateState();

            return string.Format(@"Amount of {0} has been withdrawn. Press Enter to proceed", requestAmount);
        }

        // Show only message and no change in state
        return "Invalid Amount";
    }

    private void UpdateState()
    {        
        Atm.currentState = new NoCashState(this);        
    }
}

CASH_WITHDRAWN 状态的类: 

class CashWithdrawnState : ATMState
{
    // This constructor will create new state taking values from old state
    public CashWithdrawnState(ATMState state)      
        :this(state.DummyCashPresent, state.Atm)
    {
        
    }

    // this constructor will be used by the other one
    public CashWithdrawnState(int amountRemaining, ATM atmBeingUsed)
    {
        this.Atm = atmBeingUsed;
        this.DummyCashPresent = amountRemaining;
    }

    public override string GetNextScreen()
    {
        UpdateState();
        return string.Format("Thanks you for using us, Amount left in ATM: {0}", this.DummyCashPresent.ToString());
    }

    private void UpdateState()
    {
        Atm.currentState = new NoCardState(this);
    }
}

现在,通过这种设计,我们实际上实现了与之前示例中相同的功能。这当然看起来是更多代码,但这种方法不太容易出错,并且使用这种方法,ATM 类将不需要修改。因此,如果我们需要在该系统中添加新状态 NO_CASH,我们只需添加一个新的具体类并将其与系统连接起来。 

class NoCashState : ATMState
{
    // This constructor will create new state taking values from old state
    public NoCashState(ATMState state)      
        :this(state.DummyCashPresent, state.Atm)
    {
        
    }

    // this constructor will be used by the other one
    public NoCashState(int amountRemaining, ATM atmBeingUsed)
    {
        this.Atm = atmBeingUsed;
        this.DummyCashPresent = amountRemaining;
    }

    public override string GetNextScreen()
    {            
        Console.WriteLine("ATM is EMPTY");
        Console.ReadLine();
        return string.Empty;
    }

    private void UpdateState()
    {
        // nothing here as someone will have to fill in cash and then
        // restart the atm, once restarted it will be in no card state
    }
}

以及对 CardValidatedState 类的 UpdateState 函数进行少量修改。 

private void UpdateState()
{
    if (this.DummyCashPresent == 0)
    {
        Atm.currentState = new NoCashState(this);
    }
    else
    {
        Atm.currentState = new CashWithdrawnState(this);
    }
}

现在让我们测试这个系统。


在结束之前,让我们看一下我们应用程序的类图,并将其与 GoF 的类图进行比较。 


值得关注的点:

在本文中,我们尝试对状态模式进行概述,以及何时可以使用它。我们还实现了一个小应用程序,其中包含状态模式的初步实现。

历史

  • 2012 年 11 月 5 日:第一个版本。
© . All rights reserved.