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






4.92/5 (23投票s)
本文介绍了什么是状态模式,什么时候可以发现状态模式有用,以及如何在 C# 中实现状态模式的基本实现。
引言
本文介绍了什么是状态模式,什么时候可以发现状态模式有用,以及如何在 C# 中实现状态模式的基本实现。
背景
有时我们会发现应用程序中的某些场景需要维护子系统的状态。此状态需要根据一些条件和/或用户操作进行更改。跟踪这些状态的一种方法是在代码中使用条件逻辑。
使用条件逻辑会得到我们想要的结果,但这会导致代码的可理解性降低,并且更难维护。此外,如果我们需要在系统中添加更多状态,这将非常困难,并且可能在现有系统中造成问题。
为了有效地解决这些问题,GoF 建议使用状态模式。GoF 将状态模式定义为“允许一个对象在其内部状态改变时改变它的行为。该对象看起来好像修改了它的类。”

它也可以被认为是一个面向对象的状态机。让我们看看这个模式中的主要类。
-
Context
:这是客户端访问的实际对象。需要跟踪此对象的状态。 -
State
:这是一个接口或抽象类,它定义了与 Context 的所有可能状态相关的公共行为。 -
ConcreteState
:这个类代表 Context 的一个状态。每个状态将表示为一个具体的类。
使用代码
让我们尝试实现一个小例子来详细了解状态模式。让我们尝试为一个虚拟 ATM 机实现软件。ATM 机在我们这里,银行已决定为此机器设置 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 日:第一个版本。