PHP: Знімок

Memento Знімок Memento

Знімок — це поведінковий шаблон, що дозволяє робити знімки внутрішнього стану об'єктів, а потім відновлювати їх.

При цьому Знімок не розкриває подробиць реалізації об'єктів і клієнт не має доступу до захищеної інформації об'єкта.

Детальніше про Знімок

Особливості паттерна на PHP

Складність:

Популярність:

Застосування: Реальна можливість застосування патерну Знімок в PHP під великим питанням. У багатьох випадках проблему зберігання копії стану об'єктів можна вирішити куди простіше за допомогою серіалізації.

Приклад: Структура патерну

Цей приклад показує структуру патерну Знімок, а саме — з яких класів він складається, які ролі ці класи виконують і як вони взаємодіють один з одним. Після ознайомлення зі структурою, вам буде легше сприймати наступний приклад, що розглядає реальний випадок використання патерну в світі PHP.

MementoStructural.php: Приклад структури патерну

<?php

namespace RefactoringGuru\Memento\Structural;

/**
 * The Originator holds some important state that may change over time. It also
 * defines a method for saving the state inside a memento and another method for
 * restoring the state from it.
 */
class Originator
{
    /**
     * @var mixed For the sake of simplicity, the originator's state is stored
     * inside a single variable.
     */
    private $state;

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

    /**
     * The Originator's business logic may affect its internal state. Therefore,
     * the client should backup the state before launching methods of the
     * business logic via the save() method.
     */
    public function doSomething()
    {
        print("Originator: I'm doing something important.\n");
        $this->state = $this->generateRandomString(30);
        print("Originator: and my state has changed to: {$this->state}\n");
    }

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

    /**
     * Saves the current state inside a memento.
     */
    public function save(): Memento
    {
        return new ConcreteMemento($this->state);
    }

    /**
     * Restores the Originator's state from a memento object.
     */
    public function restore(Memento $memento)
    {
        if (! $memento instanceof ConcreteMemento) {
            throw new \Exception("Unknown memento class ".get_class($memento));
        }

        $this->state = $memento->getState();
        print("Originator: My state has changed to: {$this->state}\n");
    }
}

/**
 * The Memento interface provides a way to retrieve the memento's metadata, such
 * as creation date or name. However, it doesn't expose the Originator's state.
 */
interface Memento
{
    public function getName();

    public function getDate();
}

/**
 * The Concrete Memento contains the infrastructure for storing the Originator's
 * state.
 */
class ConcreteMemento implements Memento
{
    private $state;

    private $date;

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

    /**
     * The Originator uses this method when restoring its state.
     */
    public function getState()
    {
        return $this->state;
    }

    /**
     * The rest of the methods are used by the Caretaker to display metadata.
     */
    public function getName()
    {
        return $this->date." / (".substr($this->state, 0, 9)."...)";
    }

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

/**
 * The Caretaker doesn't depend on the Concrete Memento class. Therefore, it
 * doesn't have access to the originator's state, stored inside the memento. It
 * works with all mementos via the base Memento interface.
 */
class Caretaker
{
    /**
     * @var Memento[]
     */
    private $mementos = [];

    /**
     * @var Originator
     */
    private $originator;

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

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

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

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

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

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

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

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

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

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

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

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

Output.txt: Результат виконання

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: srGIngezAEboNPDjBkuvymJKUtMSFX

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

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

Caretaker: Here's the list of mementos:
2018-06-04 14:50:39 / (Super-dup...)
2018-06-04 14:50:39 / (srGIngezA...)
2018-06-04 14:50:39 / (UwCZQaHJO...)

Client: Now, let's rollback!

Caretaker: Restoring state to: 2018-06-04 14:50:39 / (UwCZQaHJO...)
Originator: My state has changed to: UwCZQaHJOiERLlchyVuMbXNtpqTgWF

Client: Once more!

Caretaker: Restoring state to: 2018-06-04 14:50:39 / (srGIngezA...)
Originator: My state has changed to: srGIngezAEboNPDjBkuvymJKUtMSFX

Приклад: Приклад з життя

Через те, що скрипти на PHP однопоточні і виконуються в межах короткої сесії, реальне використання цього патерну в PHP виглядає для мене туманним. Хороший і вдалий приклад з реального життя, на жаль, мені ще не зустрічався. Майже будь-яку проблему, яку я можу уявити, можна вирішити легше за допомогою простої серіалізації.

Проте, якщо ви маєте хорошу ідею для прикладу, будь ласка, запропонуйте її на нашому форумі або по email support@refactoring.guru. Дякую!