在 C#中使用状态模式简化代码

科技   2025-01-10 05:31   上海  


如果你在处理处于多种状态的对象时,曾感觉被 if-else 语句或 switch 语句搞得晕头转向,那你并不孤单。这些条件判断会让代码变得一团糟——尤其是在管理对象历经不同阶段时的不同行为时更是如此。这时就该状态模式(State Pattern)登场了:它是以一种结构化、易于维护的方式来清晰管理基于状态的行为的方法。

今天,我们将以一个简单的订单处理系统为例,逐步讲解这个概念。我们会探讨如何在不堆砌 if-else 检查的情况下处理状态转换,以及状态模式如何帮助我们保持代码的整洁和可扩展性。

传统状态处理方式的问题:大量的条件判断语句

想象一个简单的订单处理系统,每个订单会经历以下几个阶段:

  • 待处理(Pending)

  • 已支付(Paid)

  • 已发货(Shipped)

  • 已送达(Delivered)

  • 已取消(Cancelled)

在管理每个状态时,我们可能希望:

  • 只对已支付的订单发货。

  • 只对已发货的订单进行派送。

  • 如果订单已送达或已取消,则阻止某些操作。

大多数人通过添加大量的 if-else 检查来处理这个问题。以下是在传统设置中它可能呈现的样子:

public class Order
{
public string Status { get; set; } = "Pending";

public void Pay()
{
if (Status == "Pending")
{
Status = "Paid";
Console.WriteLine("Order has been paid.");
}
else
{
Console.WriteLine("Cannot pay for order in current state: " + Status);
}
}

public void Ship()
{
if (Status == "Paid")
{
Status = "Shipped";
Console.WriteLine("Order has been shipped.");
}
else
{
Console.WriteLine("Cannot ship order in current state: " + Status);
}
}

public void Deliver()
{
if (Status == "Shipped")
{
Status = "Delivered";
Console.WriteLine("Order has been delivered.");
}
else
{
Console.WriteLine("Cannot deliver order in current state: " + Status);
}
}

public void Cancel()
{
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 类)所需的每个操作的方法(例如 PayShipDeliverCancel)。

public interface IOrderState
{
void Pay(Order order);
void Ship(Order order);
void Deliver(Order order);
void Cancel(Order order);
}

步骤 2:为每个状态创建类 现在,每个状态(如待处理、已支付、已发货等)都有自己的类,这些类实现 IOrderState 接口。每个类只处理在该状态下允许执行的操作。

例如,以下是待处理状态(PendingState)类可能的样子:

public class PendingState : IOrderState
{
public void Pay(Order order)
{
order.State = new PaidState();
Console.WriteLine("Order has been paid.");
}
public void Ship(Order order) => Console.WriteLine("Cannot ship a pending order.");
public void Deliver(Order order) => Console.WriteLine("Cannot deliver a pending order.");
public void Cancel(Order order)
{
order.State = new CancelledState();
Console.WriteLine("Order has been cancelled.");
}
}

以下是已支付状态(PaidState)类可能的样子:

public class PaidState : IOrderState
{
public void Pay(Order order) => Console.WriteLine("Order is already paid.");
public void Ship(Order order)
{
order.State = new ShippedState();
Console.WriteLine("Order has been shipped.");
}
public void Deliver(Order order) => Console.WriteLine("Cannot deliver a paid order.");
public void Cancel(Order order)
{
order.State = new CancelledState();
Console.WriteLine("Order has been cancelled.");
}
}

每个状态只处理对其有意义的操作,这使得每个类都很小且易于理解。

步骤 3:将订单类定义为上下文 订单类(我们的上下文)维护当前状态。它不再进行 if-else 检查,而是将工作委托给当前状态对应的类来处理。

public class Order
{
public IOrderState State { get; set; } = new PendingState();

public void Pay() => State.Pay(this);
public void Ship() => State.Ship(this);
public void Deliver() => State.Deliver(this);
public void Cancel() => 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 语句,也没有难以理解的条件判断——只有流畅的、基于状态的转换。

状态模式的优势

  • 代码条理清晰:每个状态类只处理特定状态的逻辑,所以代码更加整洁、有条理。

  • 可扩展性强:如果添加更多状态,只需添加新的类,而无需更改现有逻辑。

  • 可读性好:每个类都清晰地表明了在特定状态下可以做什么和不可以做什么,这使得代码更易于理解和调试。

何时使用基于状态的逻辑

在以下情况下,状态模式很有用:

  • 一个对象有多个状态,且每个状态都有不同的行为。

  • 根据状态执行不同操作时存在复杂的条件判断。

  • 可扩展性很重要——如果你计划添加更多状态或状态转换,这种方法将为你节省时间并减少烦恼。

使用状态模式可以使代码更易于理解、维护和扩展。我们不再处理混乱繁杂的条件判断,而是创建了一个每个状态都有明确职责的系统。这种结构有助于确保随着应用程序的发展,我们的代码依然保持整洁且易于修改。

如果你喜欢我的文章,请给我一个赞!谢谢

架构师老卢
资深软件架构师, 分享编程、软件设计经验, 教授前沿技术, 分享技术资源(每天发布电子书),每天进步一点点...
 最新文章