3.6. Aandenking

3.6.1. Doel

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).

Het aandenkenpatroon is geïmplementeerd met drie objecten: de vinder, een verzorger en een aandenken.

Aandenking - een object dat een concrete unieke momentopname van staat van een object of bron bevat: tekenreeks, getal, reeks, een instantie van klasse enzovoort. Het unieke in dit geval impliceert niet het verbod op het bestaan ​​van vergelijkbare staten in verschillende snapshots. Dat betekent dat de staat kan worden geëxtraheerd als de onafhankelijke kloon. Elk object dat in de Aandenking is opgeslagen, moet * een volledige kopie van het originele object zijn in plaats van een verwijzing * naar het originele object. Het Aandenking-object is een “ondoorzichtig object” (het object dat niemand kan of mag veranderen).

Vinder - het is een object dat de * werkelijke status van een extern object bevat, is strikt gespecificeerd type *. De maker kan een unieke kopie van deze staat maken en deze verpakt in een aandenken retourneren. De vinder kent de geschiedenis van veranderingen niet. U kunt van buitenaf een concrete toestand instellen op Vinder, die als werkelijk wordt beschouwd. De Beller moet ervoor zorgen dat de gegeven status overeenkomt met het toegestane type object. De maker kan (maar zou niet moeten) methoden hebben, maar ze * ze kunnen geen wijzigingen aanbrengen in de opgeslagen objectstatus *.

Verzorger * beheert de geschiedenis van de staten *. Hij kan wijzigingen aanbrengen in een object; een beslissing nemen om de staat van een extern object op te slaan in de Vinder; vraag aan de momentopname van de maker van de huidige staat; of stel de Vinder-status in op gelijkwaardigheid met een momentopname uit de geschiedenis.

3.6.2. Bijvoorbeeld

3.6.3. UML Diagram

Alt Momento UML Diagram

3.6.4. Code

Je kan deze broncode terugvinden op 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. Test

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}