Laravel 13 备忘录模式深度解析
备忘录模式是一种行为型设计模式,它允许在不暴露对象实现细节的情况下保存和恢复对象的内部状态。本文将深入探讨 Laravel 13 中备忘录模式的高级用法。
备忘录模式基础
什么是备忘录模式
备忘录模式提供了一种方式来捕获对象的内部状态,并在之后将其恢复,同时不违反封装原则。
1 2 3 4 5 6 7 8 9 10
| <?php
namespace App\Contracts;
interface MementoInterface { public function getState(): array;
public function getTimestamp(): \DateTime; }
|
1 2 3 4 5 6 7 8 9 10
| <?php
namespace App\Contracts;
interface OriginatorInterface { public function save(): MementoInterface;
public function restore(MementoInterface $memento): void; }
|
基础备忘录实现
备忘录类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| <?php
namespace App\Mementos;
use App\Contracts\MementoInterface;
class SnapshotMemento implements MementoInterface { protected array $state; protected \DateTime $timestamp; protected string $name;
public function __construct(string $name, array $state) { $this->name = $name; $this->state = $state; $this->timestamp = new \DateTime(); }
public function getState(): array { return $this->state; }
public function getTimestamp(): \DateTime { return $this->timestamp; }
public function getName(): string { return $this->name; } }
|
看护者类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| <?php
namespace App\Caretakers;
use App\Contracts\MementoInterface; use Illuminate\Support\Collection;
class HistoryCaretaker { protected Collection $history; protected int $maxHistory;
public function __construct(int $maxHistory = 50) { $this->history = collect(); $this->maxHistory = $maxHistory; }
public function push(MementoInterface $memento): void { $this->history->push($memento);
if ($this->history->count() > $this->maxHistory) { $this->history->shift(); } }
public function pop(): ?MementoInterface { return $this->history->pop(); }
public function get(int $index): ?MementoInterface { return $this->history->get($index); }
public function getLatest(): ?MementoInterface { return $this->history->last(); }
public function getHistory(): Collection { return $this->history; }
public function count(): int { return $this->history->count(); }
public function clear(): void { $this->history = collect(); } }
|
文本编辑器备忘录
编辑器状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?php
namespace App\Mementos\Editor;
use App\Contracts\MementoInterface;
class EditorMemento implements MementoInterface { protected string $content; protected int $cursorPosition; protected array $selection; protected \DateTime $timestamp;
public function __construct(string $content, int $cursorPosition, array $selection = []) { $this->content = $content; $this->cursorPosition = $cursorPosition; $this->selection = $selection; $this->timestamp = new \DateTime(); }
public function getState(): array { return [ 'content' => $this->content, 'cursor_position' => $this->cursorPosition, 'selection' => $this->selection, ]; }
public function getTimestamp(): \DateTime { return $this->timestamp; }
public function getContent(): string { return $this->content; }
public function getCursorPosition(): int { return $this->cursorPosition; }
public function getSelection(): array { return $this->selection; } }
|
文本编辑器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
| <?php
namespace App\Services;
use App\Contracts\MementoInterface; use App\Contracts\OriginatorInterface; use App\Mementos\Editor\EditorMemento; use App\Caretakers\HistoryCaretaker;
class TextEditor implements OriginatorInterface { protected string $content = ''; protected int $cursorPosition = 0; protected array $selection = []; protected HistoryCaretaker $history;
public function __construct() { $this->history = new HistoryCaretaker(100); }
public function type(string $text): void { $this->save();
$before = substr($this->content, 0, $this->cursorPosition); $after = substr($this->content, $this->cursorPosition);
$this->content = $before . $text . $after; $this->cursorPosition += strlen($text); }
public function delete(int $length = 1): void { $this->save();
$before = substr($this->content, 0, $this->cursorPosition - $length); $after = substr($this->content, $this->cursorPosition);
$this->content = $before . $after; $this->cursorPosition = max(0, $this->cursorPosition - $length); }
public function backspace(int $length = 1): void { $this->delete($length); }
public function setCursorPosition(int $position): void { $this->cursorPosition = max(0, min($position, strlen($this->content))); }
public function select(int $start, int $end): void { $this->selection = [ 'start' => max(0, $start), 'end' => min($end, strlen($this->content)), ]; }
public function clearSelection(): void { $this->selection = []; }
public function getContent(): string { return $this->content; }
public function getCursorPosition(): int { return $this->cursorPosition; }
public function save(): MementoInterface { $memento = new EditorMemento( $this->content, $this->cursorPosition, $this->selection );
$this->history->push($memento);
return $memento; }
public function restore(MementoInterface $memento): void { $state = $memento->getState();
$this->content = $state['content']; $this->cursorPosition = $state['cursor_position']; $this->selection = $state['selection']; }
public function undo(): bool { $memento = $this->history->pop();
if ($memento) { $this->restore($memento); return true; }
return false; }
public function canUndo(): bool { return $this->history->count() > 0; }
public function getHistory(): array { return $this->history->getHistory()->map(function ($memento) { return [ 'timestamp' => $memento->getTimestamp()->format('Y-m-d H:i:s'), 'content_preview' => substr($memento->getContent(), 0, 50), ]; })->toArray(); } }
|
表单状态备忘录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| <?php
namespace App\Mementos\Form;
use App\Contracts\MementoInterface;
class FormMemento implements MementoInterface { protected string $formId; protected array $data; protected int $step; protected array $errors; protected \DateTime $timestamp;
public function __construct(string $formId, array $data, int $step = 1, array $errors = []) { $this->formId = $formId; $this->data = $data; $this->step = $step; $this->errors = $errors; $this->timestamp = new \DateTime(); }
public function getState(): array { return [ 'form_id' => $this->formId, 'data' => $this->data, 'step' => $this->step, 'errors' => $this->errors, ]; }
public function getTimestamp(): \DateTime { return $this->timestamp; }
public function getFormId(): string { return $this->formId; }
public function getData(): array { return $this->data; }
public function getStep(): int { return $this->step; } }
|
多步骤表单
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
| <?php
namespace App\Services;
use App\Contracts\MementoInterface; use App\Contracts\OriginatorInterface; use App\Mementos\Form\FormMemento; use App\Caretakers\HistoryCaretaker; use Illuminate\Support\Facades\Session;
class MultiStepForm implements OriginatorInterface { protected string $formId; protected array $data = []; protected int $currentStep = 1; protected int $totalSteps; protected HistoryCaretaker $history;
public function __construct(string $formId, int $totalSteps = 3) { $this->formId = $formId; $this->totalSteps = $totalSteps; $this->history = new HistoryCaretaker(20);
$this->loadFromSession(); }
public function setStepData(int $step, array $data): void { $this->save();
$this->data["step_{$step}"] = $data; $this->saveToSession(); }
public function getStepData(int $step): array { return $this->data["step_{$step}"] ?? []; }
public function nextStep(): bool { if ($this->currentStep < $this->totalSteps) { $this->save(); $this->currentStep++; $this->saveToSession(); return true; }
return false; }
public function previousStep(): bool { if ($this->currentStep > 1) { $this->save(); $this->currentStep--; $this->saveToSession(); return true; }
return false; }
public function goToStep(int $step): bool { if ($step >= 1 && $step <= $this->totalSteps) { $this->save(); $this->currentStep = $step; $this->saveToSession(); return true; }
return false; }
public function getCurrentStep(): int { return $this->currentStep; }
public function getTotalSteps(): int { return $this->totalSteps; }
public function getAllData(): array { return $this->data; }
public function save(): MementoInterface { $memento = new FormMemento( $this->formId, $this->data, $this->currentStep );
$this->history->push($memento);
return $memento; }
public function restore(MementoInterface $memento): void { $state = $memento->getState();
$this->data = $state['data']; $this->currentStep = $state['step']; $this->saveToSession(); }
public function undo(): bool { $memento = $this->history->pop();
if ($memento) { $this->restore($memento); return true; }
return false; }
public function reset(): void { $this->data = []; $this->currentStep = 1; $this->history->clear(); Session::forget($this->getSessionKey()); }
protected function saveToSession(): void { Session::put($this->getSessionKey(), [ 'data' => $this->data, 'current_step' => $this->currentStep, ]); }
protected function loadFromSession(): void { $saved = Session::get($this->getSessionKey());
if ($saved) { $this->data = $saved['data'] ?? []; $this->currentStep = $saved['current_step'] ?? 1; } }
protected function getSessionKey(): string { return "form.{$this->formId}"; } }
|
游戏状态备忘录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <?php
namespace App\Mementos\Game;
use App\Contracts\MementoInterface;
class GameStateMemento implements MementoInterface { protected int $level; protected int $score; protected int $lives; protected array $inventory; protected array $position; protected \DateTime $timestamp;
public function __construct( int $level, int $score, int $lives, array $inventory, array $position ) { $this->level = $level; $this->score = $score; $this->lives = $lives; $this->inventory = $inventory; $this->position = $position; $this->timestamp = new \DateTime(); }
public function getState(): array { return [ 'level' => $this->level, 'score' => $this->score, 'lives' => $this->lives, 'inventory' => $this->inventory, 'position' => $this->position, ]; }
public function getTimestamp(): \DateTime { return $this->timestamp; }
public function getLevel(): int { return $this->level; }
public function getScore(): int { return $this->score; } }
|
游戏角色
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
| <?php
namespace App\Services\Game;
use App\Contracts\MementoInterface; use App\Contracts\OriginatorInterface; use App\Mementos\Game\GameStateMemento; use Illuminate\Support\Facades\Cache;
class GameCharacter implements OriginatorInterface { protected string $playerId; protected int $level = 1; protected int $score = 0; protected int $lives = 3; protected array $inventory = []; protected array $position = ['x' => 0, 'y' => 0];
public function __construct(string $playerId) { $this->playerId = $playerId; }
public function move(int $x, int $y): void { $this->position = ['x' => $x, 'y' => $y]; }
public function addItem(string $item): void { $this->inventory[] = $item; }
public function removeItem(string $item): bool { $index = array_search($item, $this->inventory);
if ($index !== false) { unset($this->inventory[$index]); $this->inventory = array_values($this->inventory); return true; }
return false; }
public function addScore(int $points): void { $this->score += $points; }
public function loseLife(): bool { $this->lives--;
return $this->lives > 0; }
public function levelUp(): void { $this->level++; }
public function save(): MementoInterface { $memento = new GameStateMemento( $this->level, $this->score, $this->lives, $this->inventory, $this->position );
Cache::put($this->getSaveKey(), $memento, 86400);
return $memento; }
public function restore(MementoInterface $memento): void { $state = $memento->getState();
$this->level = $state['level']; $this->score = $state['score']; $this->lives = $state['lives']; $this->inventory = $state['inventory']; $this->position = $state['position']; }
public function loadSavedGame(): bool { $memento = Cache::get($this->getSaveKey());
if ($memento) { $this->restore($memento); return true; }
return false; }
public function getLevel(): int { return $this->level; }
public function getScore(): int { return $this->score; }
public function getLives(): int { return $this->lives; }
public function getInventory(): array { return $this->inventory; }
public function getPosition(): array { return $this->position; }
protected function getSaveKey(): string { return "game.save.{$this->playerId}"; } }
|
数据库事务备忘录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
namespace App\Mementos\Database;
use App\Contracts\MementoInterface;
class TransactionMemento implements MementoInterface { protected string $transactionId; protected array $queries; protected array $snapshots; protected \DateTime $timestamp;
public function __construct(string $transactionId, array $queries, array $snapshots) { $this->transactionId = $transactionId; $this->queries = $queries; $this->snapshots = $snapshots; $this->timestamp = new \DateTime(); }
public function getState(): array { return [ 'transaction_id' => $this->transactionId, 'queries' => $this->queries, 'snapshots' => $this->snapshots, ]; }
public function getTimestamp(): \DateTime { return $this->timestamp; }
public function getQueries(): array { return $this->queries; }
public function getSnapshots(): array { return $this->snapshots; } }
|
测试备忘录模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <?php
namespace Tests\Unit\Mementos;
use Tests\TestCase; use App\Services\TextEditor; use App\Services\MultiStepForm;
class MementoTest extends TestCase { public function test_text_editor_undo(): void { $editor = new TextEditor();
$editor->type('Hello'); $editor->type(' World'); $editor->type('!');
$this->assertEquals('Hello World!', $editor->getContent());
$editor->undo(); $this->assertEquals('Hello World', $editor->getContent());
$editor->undo(); $this->assertEquals('Hello', $editor->getContent()); }
public function test_text_editor_delete(): void { $editor = new TextEditor();
$editor->type('Hello World'); $editor->setCursorPosition(5); $editor->delete(6);
$this->assertEquals('Hello', $editor->getContent());
$editor->undo(); $this->assertEquals('Hello World', $editor->getContent()); }
public function test_multi_step_form(): void { $form = new MultiStepForm('test_form', 3);
$form->setStepData(1, ['name' => 'John']); $form->nextStep();
$form->setStepData(2, ['email' => 'john@example.com']); $form->nextStep();
$this->assertEquals(3, $form->getCurrentStep());
$form->undo(); $this->assertEquals(2, $form->getCurrentStep());
$form->previousStep(); $this->assertEquals(1, $form->getCurrentStep()); } }
|
最佳实践
1. 备忘录应该不可变
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <?php
class ImmutableMemento implements MementoInterface { protected readonly array $state; protected readonly \DateTime $timestamp;
public function __construct(array $state) { $this->state = $state; $this->timestamp = new \DateTime(); }
public function getState(): array { return $this->state; }
public function getTimestamp(): \DateTime { return $this->timestamp; } }
|
2. 序列化支持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php
class SerializableMemento implements MementoInterface, \Serializable { protected array $state;
public function serialize(): string { return serialize($this->state); }
public function unserialize(string $data): void { $this->state = unserialize($data); } }
|
3. 增量备忘录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php
class IncrementalMemento implements MementoInterface { protected array $changes; protected string $baseVersion;
public function __construct(string $baseVersion, array $changes) { $this->baseVersion = $baseVersion; $this->changes = $changes; }
public function getState(): array { return [ 'base_version' => $this->baseVersion, 'changes' => $this->changes, ]; } }
|
总结
Laravel 13 的备忘录模式提供了一种优雅的方式来保存和恢复对象状态。通过合理使用备忘录模式,可以实现撤销/重做功能、状态持久化和事务回滚等功能,同时保持对象的封装性。