设计模式19-状态模式(State Pattern)
写在前面
状态变化模式
- 在组建构建过程中,某些对象的状态经常面临着变化。如何对这些变化进行有效的管理呢?同时又wage高层模块的稳定。状态变化模式为这一问题提供了一种解决方案。
- 典型模式
State
Memento
动机
- 在软件构建过程中,某些对象的状态如果改变行为也会随之而发生改变。比如文档处于只读状态,其支持的行为和读写的状态支持的行为就可能完全不同。
- 如何在运行运行根据对象的状态来透明的更改对象的行为呢?而不会为对象操作和状态转化之间引入紧耦合?
- 状态模式的动机是通过将状态相关的行为封装到独立的状态对象中,使得当对象的内部状态发生改变时,其行为也会随之改变。状态模式主要用于解决对象在多种状态下具有不同行为的问题,使得状态切换变得更加透明和可维护。
定义与结构
定义
状态模式允许对象在内部状态改变时改变其行为,对象看起来好像修改了它的类。
结构
这张图展示了一个典型的软件设计模式------状态模式(State Pattern)的架构概念。状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。在这个模式中,定义了一个状态接口(尽管在图中并未直接标出接口名称),它声明了一个或多个处理函数(Handle()),这些函数用于处理在特定状态下的请求。
图中主要包含以下几个关键组件:
-
上下文(Context):上下文类负责维护当前状态对象,并定义了一个或多个方法来改变当前状态以及触发状态处理函数。在图中,上下文通过接收一个请求(Request())来触发状态的改变,这个请求通过一个箭头指向了状态(State),表示请求是触发状态变化的原因。
-
状态(State):这是一个抽象类或接口,定义了所有可能的状态以及一个或多个处理函数(Handle())。这些处理函数用于执行与状态相关的操作。在图中,状态接口通过虚线表示,暗示它可能是抽象的,并未直接给出实现细节。
-
处理函数(Handle()):这是状态接口中定义的方法,用于在特定状态下处理请求。当上下文中的状态改变时,会调用当前状态对象的处理函数来响应请求。
-
具体状态(ConcreteStateA, ConcreteStateB):这些是实现了状态接口的具体类,每个类都实现了处理函数的具体逻辑。在图中,有两个具体状态类ConcreteStateA和ConcreteStateB,它们各自有自己的处理函数实现,但具体实现细节未在图中展示。
-
状态转换:图中通过箭头从状态指向处理函数(state->Handle())来表示当状态被激活时,相应的处理函数将被调用。这种转换是由上下文中的请求触发的,上下文负责维护当前状态,并根据请求改变状态。
总的来说,这张图通过图形化的方式展示了状态模式的核心概念,即如何根据不同的状态来改变对象的行为。通过定义状态接口和具体状态类,可以在不修改上下文类代码的情况下增加新的状态或修改现有状态的行为,从而提高了系统的可扩展性和可维护性。
C++代码推导
以下是一个使用状态模式的C++代码示例,模拟一个简单的文档工作流,包含草稿、审核和发布三个状态。
状态接口:
cpp
#include <iostream>
#include <string>
// 前向声明
class Document;
class State {
public:
virtual void handleRequest(Document* document) = 0;
virtual ~State() = default;
};
具体状态类:
cpp
class Draft : public State {
public:
void handleRequest(Document* document) override;
};
class Moderation : public State {
public:
void handleRequest(Document* document) override;
};
class Published : public State {
public:
void handleRequest(Document* document) override;
};
文档类(上下文):
cpp
class Document {
private:
State* state;
public:
Document(State* state) : state(state) {}
void setState(State* state) {
this->state = state;
}
void request() {
state->handleRequest(this);
}
void displayState(const std::string& stateName) {
std::cout << "Document is now in " << stateName << " state." << std::endl;
}
};
具体状态类实现:
cpp
void Draft::handleRequest(Document* document) {
std::cout << "Document is in draft state. Moving to moderation." << std::endl;
document->setState(new Moderation());
document->displayState("Moderation");
}
void Moderation::handleRequest(Document* document) {
std::cout << "Document is in moderation state. Moving to published." << std::endl;
document->setState(new Published());
document->displayState("Published");
}
void Published::handleRequest(Document* document) {
std::cout << "Document is already published. No further action." << std::endl;
}
客户端代码:
cpp
int main() {
Document* document = new Document(new Draft());
document->request(); // From Draft to Moderation
document->request(); // From Moderation to Published
document->request(); // Already Published
delete document;
return 0;
}
优缺点
优点:
- 简化状态转换:通过将状态转换的逻辑封装在独立的状态类中,使状态转换变得更加明确和简洁。
- 遵循开闭原则:添加新状态时,不需要修改上下文类和其他状态类,只需添加新的状态类即可。
- 减少条件判断:状态转换逻辑不再依赖于复杂的条件判断,使代码更加清晰和易于维护。
缺点:
- 增加类的数量:每个状态都需要一个具体状态类,可能会导致类的数量显著增加。
- 状态切换开销:频繁的状态切换可能带来一定的性能开销。
应用
状态模式在以下场景中应用较多:
- 对象的行为依赖于其状态:对象在不同状态下有不同的行为,并且对象的状态经常发生变化。
- 状态转换逻辑复杂:状态之间的转换逻辑复杂且易变,需要独立处理。
- 避免条件语句:需要避免在代码中出现大量的条件语句来处理状态转换逻辑。
总结
- 状态模式通过将状态相关的行为封装在独立的状态类中,使得对象在不同状态下具有不同的行为,简化了状态转换的逻辑,并提高了系统的可维护性和可扩展性。在适当的场景下使用状态模式,可以显著改善代码的可读性和灵活性。
- 动态模式将所有与一个特定状态相关的行为放入一个状态的子类对象中。在对象状态切换时切换相应的对象。同时维持状态的接口,这样实现了具体操作与状态转换之间的解耦。
- 为不同的状态引入不同的对象,使得状态转换变得更加明确。而且可以保证不会出现状态不一致的情况。因为转换是原子性的--你要么彻底转换过来,要么不转换。
- 如果状态对象没有实例变量,那么各个上下文可以共享同一个状态对象,从而节省对象开销。