备忘录模式核心结构由Originator、Memento、Caretaker三角色构成:Originator负责创建和恢复状态,Memento不可变且仅存储深拷贝的业务数据,Caretaker只存取不访问Memento内容。

PHP中备忘录模式的核心结构怎么搭?
备忘录模式不是靠某个现成函数,而是靠三个角色协同:原发器(Originator)、备忘录(Memento)、管理者(Caretaker)。PHP里没有内置 Memento 类,必须手动定义,且关键点在于——Memento 必须是**不可变的**,且原发器要能私有访问其内部状态。
常见错误是把 Memento 设计成可写对象,或让外部直接修改其属性,这会破坏状态快照的可靠性。
-
Originator负责创建和恢复状态,用createMemento()返回新Memento,用restoreFromMemento(Memento $m)回滚 -
Memento构造时接收并深拷贝当前状态(如用serialize()或json_encode()),不提供任何 setter 方法 -
Caretaker只负责存取Memento实例(例如用数组栈:$this->history = []),绝不访问或修改其内容
如何安全地序列化PHP对象状态?
直接 clone 原发器对象不行——引用类型(如数组、对象)仍共享内存;而 serialize() 是最稳妥的选择,它能处理循环引用、资源句柄(自动转为 null)和私有属性。
但要注意:含闭包、资源(curl 句柄、文件指针)或 PDO 连接的对象不能被序列化,会抛出 Serialization of 'Closure' is not allowed 错误。
- 只序列化业务数据字段(如
$this->data,$this->config),避开运行时依赖 - 在
Originator::createMemento()中显式提取需保存的字段,而非整个$this - 示例:
public function createMemento(): Memento { return new Memento([ 'data' => $this->data, 'version' => $this->version, 'timestamp' => time() ]); }
撤销操作的栈管理要注意什么?
撤销不是简单 pop 一次就完事——用户可能连续撤销后又执行新操作,这时必须清空“未来”历史(即撤销点之后的所有记录),否则 redo 逻辑会混乱。
典型陷阱是用 array_push() / array_pop() 管理,却忘了维护一个 $this->currentIndex 来标记当前有效位置。
- 推荐用索引数组 + 游标方式:
$this->history = []存所有快照,$this->current = -1表示当前未撤销 - 每次新操作后调用
$this->history = array_slice($this->history, 0, $this->current + 1)截断未来记录 - 撤销时先
$this->current--,再用$this->history[$this->current]恢复(需判断边界) - 性能上,单次撤销是 O(1),但频繁截断数组是 O(n);若历史很长,可改用 SplStack + 手动清理尾部
为什么不要在Memento里存$this引用?
很多初学者会在 Memento 构造函数里存 $originator 对象本身,以为方便恢复。这会导致两个严重问题:一是内存泄漏(循环引用使 GC 无法回收),二是状态污染(后续修改原发器会影响已存快照)。
PHP 的对象赋值默认是“写时复制”,但一旦涉及引用(&$this)或 spl_object_id 相同,快照就失效了。
-
Memento只存原始值或深拷贝后的数组/字符串/数字 - 绝对避免
new Memento($this)或$this->originator = $originator - 如果必须关联上下文,可用唯一 ID(如
uniqid())代替对象引用,在恢复时查表重建
备忘录模式的复杂性不在代码量,而在状态边界的划定——哪些该存、哪些不该碰、谁有权读写,稍一越界,撤销就变成“看似回退实则错乱”。真正难的是在业务模型里识别出那个纯净的、可序列化的“状态切片”。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











