意图
该模式可以在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
问题
现在需要实现一个编辑器,编辑器需要实现保存和恢复状态功能,怎么做到既实现这些功能,又能不暴露类的内部细节呢?
解决方案
备忘录模式将创建状态快照 (Snapshot)的工作委派给实际状态的拥有者原发器 (Originator)对象。这样其他对象就不再需要从 “外部” 复制编辑器状态了,编辑器类拥有其状态的完全访问权,因此可以自行生成快照。
模式建议将对象状态的副本存储在一个名为备忘录 (Memento) 的特殊对象中。除了创建备忘录的对象外,任何对象都不能访问备忘录的内容。其他对象必须使用受限接口与备忘录进行交互,它们可以获取快照的元数据(创建时间和操作名称等),但不能获取快照中原始对象的状态。
这种限制策略允许你将备忘录保存在通常被称为负责人(Caretakers) 的对象中。 由于负责人仅通过受限接口与备忘录互动, 故其无法修改存储在备忘录内部的状态。 同时, 原发器拥有对备忘录所有成员的访问权限, 从而能随时恢复其以前的状态。
在文字编辑器的示例中, 我们可以创建一个独立的历史 (History) 类作为负责人。编辑器每次执行操作前, 存储在负责人中的备忘录栈都会生长。你甚至可以在应用的 UI 中渲染该栈, 为用户显示之前的操作历史。
当用户触发撤销操作时, 历史类将从栈中取回最近的备忘录,并将其传递给编辑器以请求进行回滚。由于编辑器拥有对备忘录的完全访问权限,因此它可以使用从备忘录中获取的数值来替换自身的状态。
示例
class Memento {
public:
virtual std::string GetName() const = 0;
virtual std::string date() const = 0;
virtual std::string state() const = 0;
};
class ConcreteMemento : public Memento {
private:
std::string state_;
std::string date_;
public:
ConcreteMemento(std::string state) : state_(state) {
this->state_ = state;
std::time_t now = std::time(0);
this->date_ = std::ctime(&now);
}
std::string state() const override {
return this->state_;
}
std::string GetName() const override {
return this->date_ + " / (" + this->state_.substr(0, 9) + "...)";
}
std::string date() const override {
return this->date_;
}
};
class Originator {
private:
std::string state_;
std::string GenerateRandomString(int length = 10) {
const char alphanum[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
int stringLength = sizeof(alphanum) - 1;
std::string random_string;
for (int i = 0; i < length; i++) {
random_string += alphanum[std::rand() % stringLength];
}
return random_string;
}
public:
Originator(std::string state) : state_(state) {
std::cout << "Originator: My initial state is: " << this->state_ << "\n";
}
void DoSomething() {
std::cout << "Originator: I'm doing something important.\n";
this->state_ = this->GenerateRandomString(30);
std::cout << "Originator: and my state has changed to: " << this->state_ << "\n";
}
Memento *Save() {
return new ConcreteMemento(this->state_);
}
void Restore(Memento *memento) {
this->state_ = memento->state();
std::cout << "Originator: My state has changed to: " << this->state_ << "\n";
}
};
class Caretaker {
private:
std::vector<Memento *> mementos_;
Originator *originator_;
public:
Caretaker(Originator *originator) : originator_(originator) {
this->originator_ = originator;
}
void Backup() {
std::cout << "\nCaretaker: Saving Originator's state...\n";
this->mementos_.push_back(this->originator_->Save());
}
void Undo() {
if (!this->mementos_.size()) {
return;
}
Memento *memento = this->mementos_.back();
this->mementos_.pop_back();
std::cout << "Caretaker: Restoring state to: " << memento->GetName() << "\n";
try {
this->originator_->Restore(memento);
} catch (...) {
this->Undo();
}
}
void ShowHistory() const {
std::cout << "Caretaker: Here's the list of mementos:\n";
for (Memento *memento : this->mementos_) {
std::cout << memento->GetName() << "\n";
}
}
};
void ClientCode() {
Originator *originator = new Originator("Super-duper-super-puper-super.");
Caretaker *caretaker = new Caretaker(originator);
caretaker->Backup();
originator->DoSomething();
caretaker->Backup();
originator->DoSomething();
caretaker->Backup();
originator->DoSomething();
std::cout << "\n";
caretaker->ShowHistory();
std::cout << "\nClient: Now, let's rollback!\n\n";
caretaker->Undo();
std::cout << "\nClient: Once more!\n\n";
caretaker->Undo();
delete originator;
delete caretaker;
}
int main() {
std::srand(static_cast<unsigned int>(std::time(NULL)));
ClientCode();
return 0;
}
运行结果:
Originator: My initial state is: Super-duper-super-puper-super.
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: uOInE8wmckHYPwZS7PtUTwuwZfCIbz
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: te6RGmykRpbqaWo5MEwjji1fpM1t5D
Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: hX5xWDVljcQ9ydD7StUfbBt5Z7pcSN
Caretaker: Here's the list of mementos:
Sat Oct 19 18:09:37 2019
/ (Super-dup...)
Sat Oct 19 18:09:37 2019
/ (uOInE8wmc...)
Sat Oct 19 18:09:37 2019
/ (te6RGmykR...)
Client: Now, let's rollback!
Caretaker: Restoring state to: Sat Oct 19 18:09:37 2019
/ (te6RGmykR...)
Originator: My state has changed to: te6RGmykRpbqaWo5MEwjji1fpM1t5D
Client: Once more!
Caretaker: Restoring state to: Sat Oct 19 18:09:37 2019
/ (uOInE8wmc...)
Originator: My state has changed to: uOInE8wmckHYPwZS7PtUTwuwZfCIbz
备忘录模式结构
基于嵌套类的实现: 基于中间接口的实现:
小结
适合应用场景
优点
缺点
-
如果客户端过于频繁地创建备忘录,程序将消耗大量内存。 -
负责人必须完整跟踪原发器的生命周期,这样才能销毁弃用的备忘录。 -
绝大部分动态编程语言(例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。
参考
22种设计模式:refactoringguru.cn/design-patterns 《设计模式:可复用面向对象软件的基础》
|