3.6. Спомен

3.6.1. Предназначение

It provides the ability to restore an object to it’s previous state (undo via rollback) or to gain access to state of the object, without revealing it’s implementation (i.e., the object is not required to have a function to return the current state).

Шаблон е реализиран с три обекта: Originator (Оригинатор), a Caretaker (Пазител) и Memento (Спомен).

Memento - обект, който * съдържа конкретна уникална снимка на състоянието * на всеки обект или ресурс: низ, число, масив, екземпляр на клас и така нататък. Уникалността в този случай не означава забрана за съществуването на подобни състояния в различни моментни снимки. Това означава, че състояние може да бъде извлечено като независим клон. Всеки обект, съхраняван в Memento, трябва да бъде * пълно копие на оригиналния обект, а не препратка * към оригиналния обект. Обектът Memento е „непрозрачен обект“ (обектът, който никой не може или не трябва да променя).

Originator - това е обект, който съдържа * действителното състояние на външен обект е строго определен тип *. Originator е в състояние да създаде уникално копие на това състояние и да го върне увито в спомен. Originator не знае историята на промените. Можете да зададете конкретно състояние на Originator отвън, което ще се счита за действително. Originator трябва да се увери, че дадено състояние съответства на разрешения тип обект. Originator може (но не трябва) да има някакви методи, но те * не могат да правят промени в състоянието на запазения обект *.

Caretaker * контролира историята на състояния *. Той може да прави промени в обект; взима решение за запазване на състоянието на външен обект в Originator; поиска моментна снимка на текущото състояние от Originator; или зададе състоянието на Originator еквивалентно на някаква снимка от историята.

3.6.2. Примери

  • To seed генератор на псевдослучайни числа

  • Състоянието в краен автомат

  • Контрол за междинни състояния на ORM Model преди запазване

3.6.3. UML Диаграма

Alt Momento UML Diagram

3.6.4. Код

Можете също да намерите този код в GitHub

Memento.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Memento;
 6
 7class Memento
 8{
 9    public function __construct(private State $state)
10    {
11    }
12
13    public function getState(): State
14    {
15        return $this->state;
16    }
17}

State.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Memento;
 6
 7use InvalidArgumentException;
 8
 9class State implements \Stringable
10{
11    public const STATE_CREATED = 'created';
12    public const STATE_OPENED = 'opened';
13    public const STATE_ASSIGNED = 'assigned';
14    public const STATE_CLOSED = 'closed';
15
16    private string $state;
17
18    /**
19     * @var string[]
20     */
21    private static array $validStates = [
22        self::STATE_CREATED,
23        self::STATE_OPENED,
24        self::STATE_ASSIGNED,
25        self::STATE_CLOSED,
26    ];
27
28    public function __construct(string $state)
29    {
30        self::ensureIsValidState($state);
31
32        $this->state = $state;
33    }
34
35    private static function ensureIsValidState(string $state)
36    {
37        if (!in_array($state, self::$validStates)) {
38            throw new InvalidArgumentException('Invalid state given');
39        }
40    }
41
42    public function __toString(): string
43    {
44        return $this->state;
45    }
46}

Ticket.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Memento;
 6
 7/**
 8 * Ticket is the "Originator" in this implementation
 9 */
10class Ticket
11{
12    private State $currentState;
13
14    public function __construct()
15    {
16        $this->currentState = new State(State::STATE_CREATED);
17    }
18
19    public function open()
20    {
21        $this->currentState = new State(State::STATE_OPENED);
22    }
23
24    public function assign()
25    {
26        $this->currentState = new State(State::STATE_ASSIGNED);
27    }
28
29    public function close()
30    {
31        $this->currentState = new State(State::STATE_CLOSED);
32    }
33
34    public function saveToMemento(): Memento
35    {
36        return new Memento(clone $this->currentState);
37    }
38
39    public function restoreFromMemento(Memento $memento)
40    {
41        $this->currentState = $memento->getState();
42    }
43
44    public function getState(): State
45    {
46        return $this->currentState;
47    }
48}

3.6.5. Тест

Tests/MementoTest.php

 1<?php
 2
 3declare(strict_types=1);
 4
 5namespace DesignPatterns\Behavioral\Memento\Tests;
 6
 7use DesignPatterns\Behavioral\Memento\State;
 8use DesignPatterns\Behavioral\Memento\Ticket;
 9use PHPUnit\Framework\TestCase;
10
11class MementoTest extends TestCase
12{
13    public function testOpenTicketAssignAndSetBackToOpen()
14    {
15        $ticket = new Ticket();
16
17        // open the ticket
18        $ticket->open();
19        $openedState = $ticket->getState();
20        $this->assertSame(State::STATE_OPENED, (string) $ticket->getState());
21
22        $memento = $ticket->saveToMemento();
23
24        // assign the ticket
25        $ticket->assign();
26        $this->assertSame(State::STATE_ASSIGNED, (string) $ticket->getState());
27
28        // now restore to the opened state, but verify that the state object has been cloned for the memento
29        $ticket->restoreFromMemento($memento);
30
31        $this->assertSame(State::STATE_OPENED, (string) $ticket->getState());
32        $this->assertNotSame($openedState, $ticket->getState());
33    }
34}