如果你在处理处于多种状态的对象时,曾感觉被 if-else
语句或 switch
语句搞得晕头转向,那你并不孤单。这些条件判断会让代码变得一团糟——尤其是在管理对象历经不同阶段时的不同行为时更是如此。这时就该状态模式(State Pattern)登场了:它是以一种结构化、易于维护的方式来清晰管理基于状态的行为的方法。
今天,我们将以一个简单的订单处理系统为例,逐步讲解这个概念。我们会探讨如何在不堆砌 if-else
检查的情况下处理状态转换,以及状态模式如何帮助我们保持代码的整洁和可扩展性。
想象一个简单的订单处理系统,每个订单会经历以下几个阶段:
在管理每个状态时,我们可能希望:
大多数人通过添加大量的 if-else
检查来处理这个问题。以下是在传统设置中它可能呈现的样子:
public classOrder
{
publicstring Status {get;set;}="Pending";
publicvoidPay()
{
if(Status =="Pending")
{
Status ="Paid";
Console.WriteLine("Order has been paid.");
}
else
{
Console.WriteLine("Cannot pay for order in current state: "+ Status);
}
}
publicvoidShip()
{
if(Status =="Paid")
{
Status ="Shipped";
Console.WriteLine("Order has been shipped.");
}
else
{
Console.WriteLine("Cannot ship order in current state: "+ Status);
}
}
publicvoidDeliver()
{
if(Status =="Shipped")
{
Status ="Delivered";
Console.WriteLine("Order has been delivered.");
}
else
{
Console.WriteLine("Cannot deliver order in current state: "+ Status);
}
}
publicvoidCancel()
{
if(Status =="Pending"|| Status =="Paid")
{
Status ="Cancelled";
Console.WriteLine("Order has been cancelled.");
}
else
{
Console.WriteLine("Cannot cancel order in current state: "+ Status);
}
}
}
这种方法有什么问题呢?
if-else
代码块。状态模式允许对象根据其状态改变自身行为,方法是将每个状态的行为组织到各自的类中。通过这种方法:
让我们逐步进行分解。
步骤 1:创建一个状态接口 我们将定义一个接口 IOrderState
,其中包含订单类(Order
类)所需的每个操作的方法(例如 Pay
、Ship
、Deliver
、Cancel
)。
public interface IOrderState
{
void Pay(Order order);
void Ship(Order order);
void Deliver(Order order);
void Cancel(Order order);
}
步骤 2:为每个状态创建类 现在,每个状态(如待处理、已支付、已发货等)都有自己的类,这些类实现 IOrderState
接口。每个类只处理在该状态下允许执行的操作。
例如,以下是待处理状态(PendingState
)类可能的样子:
public classPendingState:IOrderState
{
publicvoidPay(Order order)
{
order.State =newPaidState();
Console.WriteLine("Order has been paid.");
}
publicvoidShip(Order order)=> Console.WriteLine("Cannot ship a pending order.");
publicvoidDeliver(Order order)=> Console.WriteLine("Cannot deliver a pending order.");
publicvoidCancel(Order order)
{
order.State =newCancelledState();
Console.WriteLine("Order has been cancelled.");
}
}
以下是已支付状态(PaidState
)类可能的样子:
public classPaidState:IOrderState
{
publicvoidPay(Order order)=> Console.WriteLine("Order is already paid.");
publicvoidShip(Order order)
{
order.State =newShippedState();
Console.WriteLine("Order has been shipped.");
}
publicvoidDeliver(Order order)=> Console.WriteLine("Cannot deliver a paid order.");
publicvoidCancel(Order order)
{
order.State =newCancelledState();
Console.WriteLine("Order has been cancelled.");
}
}
每个状态只处理对其有意义的操作,这使得每个类都很小且易于理解。
步骤 3:将订单类定义为上下文 订单类(我们的上下文)维护当前状态。它不再进行 if-else
检查,而是将工作委托给当前状态对应的类来处理。
public classOrder
{
publicIOrderState State {get;set;}=newPendingState();
publicvoidPay()=> State.Pay(this);
publicvoidShip()=> State.Ship(this);
publicvoidDeliver()=> State.Deliver(this);
publicvoidCancel()=> State.Cancel(this);
}
现在,订单类只需将请求传递给它的状态对象,然后状态对象负责处理其余的事情。
让我们看看测试时它是什么样子的。
var order = new Order();
order.Pay(); // 输出:Order has been paid.
order.Ship(); // 输出:Order has been shipped.
order.Deliver(); // 输出:Order has been delivered.
order.Cancel(); // 输出:Cannot cancel a delivered order.
没有 if-else
语句,也没有难以理解的条件判断——只有流畅的、基于状态的转换。
在以下情况下,状态模式很有用:
使用状态模式可以使代码更易于理解、维护和扩展。我们不再处理混乱繁杂的条件判断,而是创建了一个每个状态都有明确职责的系统。这种结构有助于确保随着应用程序的发展,我们的代码依然保持整洁且易于修改。
点击下方卡片关注DotNet NB