行为型设计模式-备忘录 Memento
Tags: designpatterns
Published: 2023-09-07 05:16:56



### 简介

允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态，该状态保存在对象之外，这样就可以不破坏原有对象的封装性了。

### 角色

- Originator 原发器
    
    即需要被保存状态的类
    
- 抽象 Memento 备忘录类
    
    定义备忘录基本接口
    
    可以看做成是快照 Snapshot
    
- 具体 Memento 备忘录类
    
    实现接口
    
- Caretaker 负责人
    
    即连接Originator和Memento的类，维护一个Memento队列，通过操作队列实现undo redo的操作
    

### 类图

图中，ConcreteOriginator通过save方法，生成一个Memento，Caretaker 的history属性保存这些Memento，实现undo操作。

![类图](https://deb-gmi.oss-cn-beijing.aliyuncs.com/img/20210409115510.png)

### 代码

```php
class Originator
{
    private $state;

    public function __construct(string $state)
    {
        $this->state = $state;
        echo "Originator: My initial state is: {$this->state}\n";
    }

    public function doSomething(): void
    {
        echo "Originator: I'm doing something important.\n";
        $this->state = $this->generateRandomString(30);
        echo "Originator: and my state has changed to: {$this->state}\n";
    }

    private function generateRandomString(int $length = 10): string
    {
        return substr(
            str_shuffle(
                str_repeat(
                    $x = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
                    ceil($length / strlen($x))
                )
            ),
            1,
            $length
        );
    }

    public function save(): Memento
    {
        return new ConcreteMemento($this->state);
    }

    public function restore(Memento $memento): void
    {
        $this->state = $memento->getState();
        echo "Originator: My state has changed to: {$this->state}\n";
    }
}

interface Memento
{
    public function getName(): string;

    public function getDate(): string;
}

class ConcreteMemento implements Memento
{
    private $state;

    private $date;

    public function __construct(string $state)
    {
        $this->state = $state;
        $this->date = date('Y-m-d H:i:s');
    }

    public function getState(): string
    {
        return $this->state;
    }

    public function getName(): string
    {
        return $this->date . " / (" . substr($this->state, 0, 9) . "...)";
    }

    public function getDate(): string
    {
        return $this->date;
    }
}

class Caretaker
{
    private $mementos = [];

    private $originator;

    public function __construct(Originator $originator)
    {
        $this->originator = $originator;
    }

    public function backup(): void
    {
        echo "\nCaretaker: Saving Originator's state...\n";
        $this->mementos[] = $this->originator->save();
    }

    public function undo(): void
    {
        if (!count($this->mementos)) {
            return;
        }
        $memento = array_pop($this->mementos);

        echo "Caretaker: Restoring state to: " . $memento->getName() . "\n";
        try {
            $this->originator->restore($memento);
        } catch (\Exception $e) {
            $this->undo();
        }
    }

    public function showHistory(): void
    {
        echo "Caretaker: Here's the list of mementos:\n";
        foreach ($this->mementos as $memento) {
            echo $memento->getName() . "\n";
        }
    }
}

$originator = new Originator("Super-duper-super-puper-super.");
$caretaker = new Caretaker($originator);

$caretaker->backup();
$originator->doSomething();

$caretaker->backup();
$originator->doSomething();

$caretaker->backup();
$originator->doSomething();

echo "\n";
$caretaker->showHistory();

echo "\nClient: Now, let's rollback!\n\n";
$caretaker->undo();

echo "\nClient: Once more!\n\n";
$caretaker->undo();
```

output:

```
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: uDdcJyRkbMEFqaKnpHiYQtrzWjXPVI

Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: YrymDJvtFqlwGpMuCNXadsQonIVPzR

Caretaker: Saving Originator's state...
Originator: I'm doing something important.
Originator: and my state has changed to: pOuLBUelsmFRkKrbhAGfqzTtQXjnNH

Caretaker: Here's the list of mementos:
2023-06-28 01:51:01 / (Super-dup...)
2023-06-28 01:51:01 / (uDdcJyRkb...)
2023-06-28 01:51:01 / (YrymDJvtF...)

Client: Now, let's rollback!

Caretaker: Restoring state to: 2023-06-28 01:51:01 / (YrymDJvtF...)
Originator: My state has changed to: YrymDJvtFqlwGpMuCNXadsQonIVPzR

Client: Once more!

Caretaker: Restoring state to: 2023-06-28 01:51:01 / (uDdcJyRkb...)
Originator: My state has changed to: uDdcJyRkbMEFqaKnpHiYQtrzWjXPVI
```