在复杂的业务逻辑中,对象可能需要在多个状态之间切换,并且不同的状态下行为也各不相同。如果使用大量的 if-else 或 switch-case 来处理这些状态转换,会导致代码变得难以维护和扩展,这就是典型的状态爆炸问题。今天我们深入探讨 C++ 设计模式——状态模式(State),并提供实际的代码示例,帮助你解决这些问题。
问题场景:订单状态流转
假设我们正在开发一个电商系统,订单有以下几种状态:待支付、已支付、待发货、已发货、已完成、已取消。不同的状态下,订单可以执行不同的操作,例如:
待支付:可以支付、取消订单已支付:可以申请退款、提醒发货待发货:可以修改收货地址已发货:可以确认收货已完成:可以评价、申请售后已取消:无法执行任何操作
如果用传统的 if-else 或 switch-case 来实现,代码会变得非常冗长且难以维护。例如:
enum class OrderStatus {
PENDING_PAYMENT,
PAID,
PENDING_DELIVERY,
DELIVERED,
COMPLETED,
CANCELLED
};
class Order {
public:
OrderStatus getStatus() const { return status; }
void pay() {
if (status == OrderStatus::PENDING_PAYMENT) {
// 执行支付逻辑
status = OrderStatus::PAID;
std::cout << "Payment successful!" << std::endl;
} else {
std::cout << "Cannot pay in current status." << std::endl;
}
}
void deliver() {
if (status == OrderStatus::PAID) {
// 执行发货逻辑
status = OrderStatus::PENDING_DELIVERY;
std::cout << "Order delivered!" << std::endl;
} else {
std::cout << "Cannot deliver in current status." << std::endl;
}
}
private:
OrderStatus status = OrderStatus::PENDING_PAYMENT;
};
随着状态和操作的增加,这种代码会迅速膨胀,变得难以理解和修改。
状态模式的解决方案
状态模式的核心思想是将每个状态封装成一个独立的类,并将与状态相关的行为封装在状态类中。Context 类持有当前状态的引用,并将请求委托给当前状态对象处理。
- 定义状态接口:
class OrderState {
public:
virtual void pay(Order* order) {}
virtual void deliver(Order* order) {}
virtual void complete(Order* order) {}
virtual void cancel(Order* order) {}
virtual ~OrderState() = default; // 虚析构函数,确保多态正确析构
};
- 实现具体状态类:
class PendingPaymentState : public OrderState {
public:
void pay(Order* order) override {
std::cout << "Payment processing..." << std::endl;
order->setState(new PaidState());
std::cout << "Payment successful!" << std::endl;
delete this; // 状态转移后,销毁自身
}
void cancel(Order* order) override {
std::cout << "Order cancelled." << std::endl;
order->setState(new CancelledState());
delete this;
}
};
class PaidState : public OrderState {
public:
void deliver(Order* order) override {
std::cout << "Delivering order..." << std::endl;
order->setState(new PendingDeliveryState());
std::cout << "Order delivered!" << std::endl;
delete this;
}
};
class PendingDeliveryState : public OrderState {
public:
void complete(Order* order) override {
std::cout << "Completing order..." << std::endl;
order->setState(new CompletedState());
std::cout << "Order completed!" << std::endl;
delete this;
}
};
class CompletedState : public OrderState {
public:
void cancel(Order* order) override {
std::cout << "Cancelling order in completed state is not supported." << std::endl;
}
};
class CancelledState : public OrderState {
public:
void pay(Order* order) override {
std::cout << "Paying a cancelled order is not supported." << std::endl;
}
};
- 定义 Context 类(Order 类):
class Order {
public:
Order() : state(new PendingPaymentState()) {}
void setState(OrderState* newState) {
state = newState;
}
void pay() {
state->pay(this);
}
void deliver() {
state->deliver(this);
}
void complete() {
state->complete(this);
}
void cancel() {
state->cancel(this);
}
~Order() {
delete state;
}
private:
OrderState* state; // 当前状态
};
- 客户端代码:
int main() {
Order order;
order.pay();
order.deliver();
order.complete();
order.cancel(); // 不支持,但不会崩溃
return 0;
}
底层原理剖析
状态模式本质上是将状态的定义和状态转换的逻辑解耦,使得每个状态类只关注自身的行为,而 Context 类只负责维护当前状态。这符合单一职责原则和开闭原则,使得代码更容易维护和扩展。
状态模式通过多态来实现状态转换。Context 类持有的是抽象状态接口的指针,因此可以根据需要切换到不同的具体状态类,而无需修改 Context 类的代码。
值得注意的是,状态的切换,尤其是在高并发场景下,需要考虑线程安全问题。例如,需要使用互斥锁 (mutex) 来保护状态变量的访问和修改。此外,状态模式也常与单例模式结合使用,以保证每个状态只有一个实例。
实战避坑经验总结
- 状态切换的内存管理:在状态切换时,需要手动
delete原来的状态对象,否则会导致内存泄漏。 可以使用智能指针,例如std::unique_ptr,来自动管理状态对象的生命周期。如代码中所示,每次状态转移后delete this,确保旧状态被释放。 - 状态类的粒度:状态类的粒度需要根据实际情况进行权衡。如果状态过于细粒度,会导致类的数量过多;如果状态过于粗粒度,会导致状态类过于复杂。通常,可以将具有相似行为的状态合并成一个状态类。
- 线程安全问题:在高并发场景下,需要考虑线程安全问题。可以使用互斥锁来保护状态变量的访问和修改,避免出现竞态条件。
- 状态持久化:如果需要将状态持久化到数据库或其他存储介质中,需要考虑如何序列化和反序列化状态对象。可以使用 JSON 或 Protocol Buffers 等序列化框架。
状态模式的适用场景:
- 当一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 当一个操作中含有庞大的多分支条件语句,且这些分支依赖于该对象的状态时。
在实际项目中,还需要结合具体的业务场景进行灵活运用。例如,在游戏开发中,可以使用状态模式来管理角色的状态(例如:站立、行走、跳跃、攻击等);在网络编程中,可以使用状态模式来管理连接的状态(例如:连接中、已连接、已断开等)。在 Nginx 的源码中,也能看到状态模式的一些影子,虽然没有直接套用,但状态机的思想无处不在,例如处理 HTTP 请求的不同阶段。
希望这篇文章能够帮助你更好地理解和应用 C++ 设计模式——状态模式(State),写出更清晰、易维护的代码!
冠军资讯
脱发程序员