本文目录导读:

我来为您介绍如何在PHP项目中实现备忘录(Memento)模式和撤销功能。
备忘录模式实现
基础备忘录类
<?php
/**
* 备忘录类 - 存储状态快照
*/
class Memento
{
private $state;
private $timestamp;
public function __construct($state)
{
$this->state = $state;
$this->timestamp = time();
}
public function getState()
{
return $this->state;
}
public function getTimestamp()
{
return $this->timestamp;
}
public function getName()
{
return date('Y-m-d H:i:s', $this->timestamp);
}
}
/**
* 发起人 - 创建和恢复备忘录
*/
class Originator
{
private $state;
public function setState($state)
{
echo "设置状态为: " . $state . "\n";
$this->state = $state;
}
public function getState()
{
return $this->state;
}
// 保存状态到备忘录
public function saveStateToMemento()
{
echo "保存状态: " . $this->state . "\n";
return new Memento($this->state);
}
// 从备忘录恢复状态
public function getStateFromMemento(Memento $memento)
{
$this->state = $memento->getState();
echo "恢复到状态: " . $this->state . "\n";
}
}
/**
* 管理者 - 管理备忘录历史记录
*/
class Caretaker
{
private $mementoList = [];
private $currentIndex = -1;
private $maxHistory = 50; // 最大历史记录数
// 添加备忘录
public function add(Memento $memento)
{
// 如果在撤销后添加新操作,删除当前索引后的所有记录
if ($this->currentIndex < count($this->mementoList) - 1) {
array_splice($this->mementoList, $this->currentIndex + 1);
}
$this->mementoList[] = $memento;
// 限制历史记录数量
if (count($this->mementoList) > $this->maxHistory) {
array_shift($this->mementoList);
}
$this->currentIndex = count($this->mementoList) - 1;
}
// 获取上一个状态(撤销)
public function undo()
{
if ($this->currentIndex > 0) {
$this->currentIndex--;
return $this->mementoList[$this->currentIndex];
}
return null;
}
// 获取下一个状态(重做)
public function redo()
{
if ($this->currentIndex < count($this->mementoList) - 1) {
$this->currentIndex++;
return $this->mementoList[$this->currentIndex];
}
return null;
}
// 获取当前状态
public function getCurrent()
{
if ($this->currentIndex >= 0 && $this->currentIndex < count($this->mementoList)) {
return $this->mementoList[$this->currentIndex];
}
return null;
}
// 获取历史记录数量
public function getHistoryCount()
{
return count($this->mementoList);
}
// 获取当前索引
public function getCurrentIndex()
{
return $this->currentIndex;
}
// 是否可以撤销
public function canUndo()
{
return $this->currentIndex > 0;
}
// 是否可以重做
public function canRedo()
{
return $this->currentIndex < count($this->mementoList) - 1;
}
// 清空历史
public function clear()
{
$this->mementoList = [];
$this->currentIndex = -1;
}
}
实际应用示例 - 文本编辑器
<?php
/**
* 文本编辑器 - 使用备忘录模式实现撤销/重做
*/
class TextEditor
{
private $content = '';
private $caretaker;
private $originator;
public function __construct()
{
$this->caretaker = new Caretaker();
$this->originator = new Originator();
}
// 写入文本
public function write($text)
{
// 保存当前状态
$this->saveState();
$this->content .= $text;
echo "写入: '" . $text . "'\n";
}
// 删除文本
public function delete($length = null)
{
$this->saveState();
if ($length === null) {
$deleted = substr($this->content, -1);
$this->content = substr($this->content, 0, -1);
echo "删除: '" . $deleted . "'\n";
} else {
$deleted = substr($this->content, -$length);
$this->content = substr($this->content, 0, -$length);
echo "删除: '" . $deleted . "'\n";
}
}
// 替换文本
public function replace($search, $replace)
{
$this->saveState();
$this->content = str_replace($search, $replace, $this->content);
echo "替换: '" . $search . "' -> '" . $replace . "'\n";
}
// 清空文本
public function clear()
{
$this->saveState();
$this->content = '';
echo "清空文本\n";
}
// 保存当前状态
private function saveState()
{
$this->originator->setState($this->content);
$this->caretaker->add($this->originator->saveStateToMemento());
}
// 撤销
public function undo()
{
$memento = $this->caretaker->undo();
if ($memento) {
$this->content = $memento->getState();
echo "撤销操作\n";
} else {
echo "无法撤销\n";
}
}
// 重做
public function redo()
{
$memento = $this->caretaker->redo();
if ($memento) {
$this->content = $memento->getState();
echo "重做操作\n";
} else {
echo "无法重做\n";
}
}
// 获取当前内容
public function getContent()
{
return $this->content;
}
// 显示状态信息
public function getStatus()
{
return [
'content' => $this->content,
'canUndo' => $this->caretaker->canUndo(),
'canRedo' => $this->caretaker->canRedo(),
'historyCount' => $this->caretaker->getHistoryCount()
];
}
}
// 使用示例
$editor = new TextEditor();
echo "=== 文本编辑器演示 ===\n\n";
$editor->write("Hello");
echo "内容: '" . $editor->getContent() . "'\n\n";
$editor->write(" World");
echo "内容: '" . $editor->getContent() . "'\n\n";
$editor->write("!");
echo "内容: '" . $editor->getContent() . "'\n\n";
// 撤销两次
$editor->undo();
echo "内容: '" . $editor->getContent() . "'\n\n";
$editor->undo();
echo "内容: '" . $editor->getContent() . "'\n\n";
// 重做一次
$editor->redo();
echo "内容: '" . $editor->getContent() . "'\n\n";
数据库操作撤销
<?php
/**
* 数据库操作撤销管理器
*/
class DatabaseOperationManager
{
private $history = [];
private $currentIndex = -1;
private $pdo;
public function __construct(PDO $pdo)
{
$this->pdo = $pdo;
}
// 执行数据库操作
public function execute($sql, $params = [], $rollbackSql = null)
{
// 开始事务
$this->pdo->beginTransaction();
try {
$stmt = $this->pdo->prepare($sql);
$stmt->execute($params);
// 保存操作记录
$operation = [
'sql' => $sql,
'params' => $params,
'rollbackSql' => $rollbackSql,
'timestamp' => time()
];
// 如果在撤销后添加新操作,删除当前索引后的记录
if ($this->currentIndex < count($this->history) - 1) {
array_splice($this->history, $this->currentIndex + 1);
}
$this->history[] = $operation;
$this->currentIndex = count($this->history) - 1;
$this->pdo->commit();
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
throw $e;
}
}
// 撤销操作
public function undo()
{
if ($this->currentIndex < 0) {
return false;
}
$operation = $this->history[$this->currentIndex];
if ($operation['rollbackSql']) {
$this->pdo->beginTransaction();
try {
$stmt = $this->pdo->prepare($operation['rollbackSql']);
$stmt->execute();
$this->pdo->commit();
$this->currentIndex--;
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
throw $e;
}
}
return false;
}
// 重做操作
public function redo()
{
$nextIndex = $this->currentIndex + 1;
if ($nextIndex >= count($this->history)) {
return false;
}
$operation = $this->history[$nextIndex];
$this->pdo->beginTransaction();
try {
$stmt = $this->pdo->prepare($operation['sql']);
$stmt->execute($operation['params']);
$this->pdo->commit();
$this->currentIndex = $nextIndex;
return true;
} catch (Exception $e) {
$this->pdo->rollBack();
throw $e;
}
}
// 获取操作历史
public function getHistory()
{
$history = [];
for ($i = 0; $i <= $this->currentIndex; $i++) {
$op = $this->history[$i];
$history[] = [
'index' => $i,
'time' => date('Y-m-d H:i:s', $op['timestamp']),
'sql' => $op['sql']
];
}
return $history;
}
}
完整的UndoManager类
<?php
/**
* 通用撤销管理器
*/
class UndoManager
{
private static $instance;
private $stacks = [];
private function __construct() {}
public static function getInstance()
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
// 为特定操作创建堆栈
public function createStack($name)
{
if (!isset($this->stacks[$name])) {
$this->stacks[$name] = [
'undo' => [],
'redo' => [],
'maxSize' => 50
];
}
}
// 执行操作并保存撤销信息
public function execute($stackName, $action, $undoData)
{
if (!isset($this->stacks[$stackName])) {
$this->createStack($stackName);
}
$stack = &$this->stacks[$stackName];
// 清空重做堆栈(新操作后不能重做)
$stack['redo'] = [];
// 添加到撤销堆栈
$stack['undo'][] = [
'action' => $action,
'data' => $undoData,
'timestamp' => time()
];
// 限制堆栈大小
if (count($stack['undo']) > $stack['maxSize']) {
array_shift($stack['undo']);
}
}
// 撤销
public function undo($stackName)
{
if (!isset($this->stacks[$stackName]) || empty($this->stacks[$stackName]['undo'])) {
return null;
}
$stack = &$this->stacks[$stackName];
$operation = array_pop($stack['undo']);
// 添加到重做堆栈
$stack['redo'][] = $operation;
return $operation;
}
// 重做
public function redo($stackName)
{
if (!isset($this->stacks[$stackName]) || empty($this->stacks[$stackName]['redo'])) {
return null;
}
$stack = &$this->stacks[$stackName];
$operation = array_pop($stack['redo']);
// 添加到撤销堆栈
$stack['undo'][] = $operation;
return $operation;
}
// 检查是否可以撤销
public function canUndo($stackName)
{
return isset($this->stacks[$stackName]) && !empty($this->stacks[$stackName]['undo']);
}
// 检查是否可以重做
public function canRedo($stackName)
{
return isset($this->stacks[$stackName]) && !empty($this->stacks[$stackName]['redo']);
}
// 清空堆栈
public function clearStack($stackName)
{
if (isset($this->stacks[$stackName])) {
$this->stacks[$stackName]['undo'] = [];
$this->stacks[$stackName]['redo'] = [];
}
}
// 获取堆栈状态
public function getStackStatus($stackName)
{
if (!isset($this->stacks[$stackName])) {
return null;
}
return [
'undoCount' => count($this->stacks[$stackName]['undo']),
'redoCount' => count($this->stacks[$stackName]['redo']),
'canUndo' => $this->canUndo($stackName),
'canRedo' => $this->canRedo($stackName)
];
}
}
实际应用示例
<?php
// 示例1:文档编辑器
class Document
{
private $content = '';
private $undoManager;
public function __construct()
{
$this->undoManager = UndoManager::getInstance();
$this->undoManager->createStack('document');
}
public function insert($text)
{
$oldContent = $this->content;
$this->content .= $text;
$this->undoManager->execute('document', 'insert', [
'oldContent' => $oldContent,
'newContent' => $this->content
]);
echo "插入: '$text'\n";
}
public function undo()
{
$operation = $this->undoManager->undo('document');
if ($operation) {
$data = $operation['data'];
$this->content = $data['oldContent'];
echo "撤销插入\n";
}
}
public function redo()
{
$operation = $this->undoManager->redo('document');
if ($operation) {
$data = $operation['data'];
$this->content = $data['newContent'];
echo "重做插入\n";
}
}
public function getContent()
{
return $this->content;
}
}
// 使用示例
$doc = new Document();
$doc->insert("Hello");
$doc->insert(" World");
$doc->insert("!");
echo "内容: " . $doc->getContent() . "\n";
$doc->undo();
echo "撤销后: " . $doc->getContent() . "\n";
$doc->undo();
echo "再次撤销: " . $doc->getContent() . "\n";
$doc->redo();
echo "重做后: " . $doc->getContent() . "\n";
最佳实践建议
性能优化
<?php
/**
* 带压缩的备忘录
*/
class CompressedMemento
{
private $compressedState;
private $timestamp;
public function __construct($state)
{
// 压缩状态以节省内存
$this->compressedState = gzcompress(serialize($state));
$this->timestamp = time();
}
public function getState()
{
return unserialize(gzuncompress($this->compressedState));
}
}
/**
* 差异备忘录 - 只存储变化部分
*/
class DiffMemento
{
private $diff;
private $timestamp;
public function __construct($oldState, $newState)
{
// 计算差异
$this->diff = $this->computeDiff($oldState, $newState);
$this->timestamp = time();
}
private function computeDiff($old, $new)
{
// 简单差异计算
if (is_string($old) && is_string($new)) {
$minLen = min(strlen($old), strlen($new));
$diffStart = 0;
for ($i = 0; $i < $minLen; $i++) {
if ($old[$i] !== $new[$i]) {
$diffStart = $i;
break;
}
}
return [
'start' => $diffStart,
'deleted' => substr($old, $diffStart),
'inserted' => substr($new, $diffStart)
];
}
return ['old' => $old, 'new' => $new];
}
}
清理策略
<?php
/**
* 自动清理的备忘录管理器
*/
class AutoCleanCaretaker
{
private $mementos = [];
private $maxSize = 100;
private $timeToLive = 3600; // 1小时
public function add(Memento $memento)
{
$this->cleanup();
$this->mementos[] = $memento;
if (count($this->mementos) > $this->maxSize) {
array_shift($this->mementos);
}
}
private function cleanup()
{
$now = time();
$this->mementos = array_filter($this->mementos, function($memento) use ($now) {
return ($now - $memento->getTimestamp()) < $this->timeToLive;
});
}
}
这个实现提供了:
- 基础的备忘录模式 - 保存和恢复对象状态
- 完整的撤销/重做功能 - 支持多级撤销和重做
- 历史记录管理 - 限制历史记录数量,防止内存溢出
- 实际应用示例 - 文本编辑器、数据库操作等
- 性能优化 - 压缩、差异存储等
- 线程安全 - 使用单例模式
使用注意事项:
- 合理设置历史记录上限
- 考虑内存消耗,特别是大对象的序列化
- 实现清理策略,定期清理过期记录
- 注意对象克隆的深度问题