前言
行为型模式关注对象之间的交互以及如何分配职责,提供了一种定义对象之间的行为和职责的最佳方式。本章介绍创建型模式中的备忘录模式和状态模式。
19. 备忘录模式(Memento)
定义
备忘录模式允许在不破坏封装性的前提下,捕获一个对象的内部状态并在该对象之外保存这个状态。这样,就可以在以后将该对象恢复到原先保存的状态。
问题
在软件开发中,有时需要保存一个对象在某个时刻的状态,以便在将来某个时刻能够恢复到这个状态。直接保存对象的状态可能会导致对象内部结构的暴露,从而破坏封装性。
解决方案
备忘录模式通过以下步骤来解决问题:
- 定义备忘录接口(Memento):创建一个接口,用于保存原发器(Originator)对象的内部状态。这个接口通常只包含一个方法来获取备忘录中的状态信息,但实际的保存状态信息操作由原发器类负责。
- 具体备忘录类(Concrete Memento):实现备忘录接口,负责存储原发器的内部状态。具体备忘录类通常包含一些私有字段来保存原发器的状态信息。
- 原发器类(Originator):需要保存其内部状态的对象。原发器类创建一个包含当前内部状态的备忘录对象,并使用一个公共的接口将这个备忘录对象传递给其他的对象(如负责人)。同时,原发器类还提供一个恢复状态的方法,该方法使用备忘录对象来恢复原发器的内部状态。
- 负责人(Caretaker):负责保存备忘录对象,但不检查备忘录对象的内容。负责人可以根据需要将备忘录对象回传给原发器。
#include <iostream>
#include <string>
#include <memory>
#include <stack>
// 备忘录接口
class Memento {
public:
virtual ~Memento() {}
// 通常这里会有一个方法来获取状态,但在这个例子中,我们直接在ConcreteMemento中处理
};
// 具体备忘录类,存储编辑器的内容状态
class TextMemento : public Memento {
private:
std::string content;
public:
TextMemento(const std::string& content) : content(content) {}
// 获取内容的方法,这里直接返回内容,实际中可能通过接口返回
std::string GetContent() const {
return content;
}
};
// 原发器类,编辑器
class TextEditor {
private:
std::string content;
public:
void Type(const std::string& text) {
content += text;
std::cout << "Typed: " << text << std::endl;
}
// 创建备忘录
std::shared_ptr<Memento> CreateMemento() {
return std::make_shared<TextMemento>(content);
}
// 从备忘录恢复状态
void RestoreMemento(const std::shared_ptr<Memento>& memento) {
auto textMemento = std::dynamic_pointer_cast<TextMemento>(memento);
if (textMemento) {
content = textMemento->GetContent();
std::cout << "Restored content: " << content << std::endl;
}
}
// 获取当前内容
std::string GetContent() const {
return content;
}
};
// 负责人,管理备忘录
class Caretaker {
private:
std::stack<std::shared_ptr<Memento>> mementos;
public:
void AddMemento(const std::shared_ptr<Memento>& memento) {
mementos.push(memento);
}
std::shared_ptr<Memento> GetMemento() {
if (!mementos.empty()) {
std::shared_ptr<Memento> top = mementos.top();
mementos.pop();
return top;
}
return nullptr;
}
};
// 客户端代码
int main() {
TextEditor editor;
Caretaker caretaker;
editor.Type("Hello, ");
caretaker.AddMemento(editor.CreateMemento()); // 保存状态
editor.Type("World!");
std::cout << "Current content: " << editor.GetContent() << std::endl; // 输出: Hello, World!
// 撤销操作
editor.RestoreMemento(caretaker.GetMemento());
std::cout << "Restored content: " << editor.GetContent() << std::endl; // 输出: Hello,
return 0;
}
应用场景
- 需要保存和恢复对象状态的场景。
- 提供一个可撤销操作的场景。
- 需要防止外部对象直接访问对象内部状态的场景。
优缺点
优点:
- 封装性好:备忘录模式通过将状态信息封装在备忘录对象中,使得原发器对象不需要将状态信息暴露给外部对象,从而保持了良好的封装性。
- 灵活性高:用户可以根据需要保存和恢复对象的状态多次,增加了软件的灵活性。特别是当需要实现复杂的撤销/重做功能时,备忘录模式提供了很好的支持。
- 符合单一职责原则:备忘录对象负责管理原发器的内部状态,使得原发器类更加简洁,符合单一职责原则。
缺点:
- 资源消耗:如果原发器对象的状态信息较多,那么每次保存状态都需要创建和存储一个完整的备忘录对象,这可能会消耗较多的内存资源。
- 设计复杂:实现备忘录模式需要定义多个类,包括备忘录接口、具体备忘录类、原发器类和负责人类等,增加了系统的复杂性。
- 适用范围有限:虽然备忘录模式可以解决需要保存和恢复对象状态的场景,但它并不是所有场景下的最佳选择。在某些情况下,可能需要考虑其他设计模式或解决方案。
20. 状态模式(State)
定义
状态模式允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式将特定状态相关的行为局部化,并将不同状态的行为分割开来,使得每一种状态对应一个类的行为。
问题
在软件开发中,一个对象的行为可能会随着其内部状态的变化而变化。如果直接在对象中嵌入这些状态相关的行为,会导致对象变得庞大且难以维护。
解决方案
状态模式通过以下步骤来解决问题:
- 定义状态接口(State):创建一个接口以封装与上下文(Context)的一个特定状态相关的行为。
- 具体状态类(Concrete States):实现状态接口,每个类代表一个具体的状态,并在其中实现与状态相关的行为。
- 上下文类(Context):维护一个状态对象的实例,这个状态对象定义了当前的状态。上下文类负责状态的切换,并将状态相关的请求委托给当前的状态对象来处理。
#include <iostream>
class TrafficLight;
// 状态接口
class TrafficLightState {
public:
virtual ~TrafficLightState() {}
virtual void Change(TrafficLight* context) = 0;
};
// 具体状态类:红灯
class RedLightState : public TrafficLightState {
public:
void Change(TrafficLight* context) override;
};
// 具体状态类:绿灯
class GreenLightState : public TrafficLightState {
public:
void Change(TrafficLight* context) override;
};
// 具体状态类:黄灯
class YellowLightState : public TrafficLightState {
public:
void Change(TrafficLight* context) override;
};
// 上下文类:交通信号灯
class TrafficLight {
private:
TrafficLightState* currentState;
RedLightState* redLightState;
GreenLightState* greenLightState;
YellowLightState* yellowLightState;
public:
TrafficLight() {
redLightState = new RedLightState();
greenLightState = new GreenLightState();
yellowLightState = new YellowLightState();
currentState = redLightState; // 初始状态为红灯
}
~TrafficLight() {
delete redLightState;
delete greenLightState;
delete yellowLightState;
}
void SetState(TrafficLightState* state) {
currentState = state;
}
TrafficLightState* GetRedLightState() {
return redLightState;
}
TrafficLightState* GetGreenLightState() {
return greenLightState;
}
TrafficLightState* GetYellowLightState() {
return yellowLightState;
}
void Change() {
currentState->Change(this);
}
};
void RedLightState::Change(TrafficLight* context) {
std::cout << "Red light is on. Cars must stop." << std::endl;
context->SetState(context->GetGreenLightState());
}
void GreenLightState::Change(TrafficLight* context) {
std::cout << "Green light is on. Cars may go." << std::endl;
context->SetState(context->GetYellowLightState());
}
void YellowLightState::Change(TrafficLight* context) {
std::cout << "Yellow light is on. Cars must prepare to stop." << std::endl;
context->SetState(context->GetRedLightState());
}
// 客户端代码
int main() {
TrafficLight light;
for (int i = 0; i < 10; ++i) {
light.Change(); // 模拟信号灯变化
}
return 0;
}
应用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
优缺点
优点:
- 封装了状态的转换规则:状态模式将状态的转换代码封装在环境类或具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
- 结构清晰:将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
- 减少对象间的相互依赖:将不同的状态引入独立的对象中会使得状态转换变得更加明确,同时减少了对象间的依赖关系。
- 容易扩展:状态模式允许通过定义新的子类来容易地增加新的状态和转换,从而提高了系统的可扩展性。
缺点:
- 增加类和对象的个数:状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
- 结构与实现复杂:状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
- 对开闭原则的支持不佳:对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态。同时,修改某个状态类的行为也需要修改对应类的源代码,这违反了开闭原则(软件实体应当对扩展开放,对修改关闭)。
…
To be continued.