НЕ МОЛЧИ!!!    Сделай что-нибудь,
чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Prototype (Прототип)

Представлен шаблон проектирования Prototype или Прототип, приводится описание шаблона, его применение и пример реализации на PHP.

Содержание

  • Применение
  • Описание шаблона
  • Реализация
  • Итоги

Применение

Структурный шаблон проектирования Prototype используется как альтернатива созданию объектов через ключевое слово new. Когда это оправдано? Например, если объект имеет множество полей, проще будет создать его копию и переназначить значения необходимых, вместо того, чтобы заново инициализировать все свойства нового объекта. Или, когда нет желания строить иерархии фабрик, параллельные иерархии классов продуктов, как это делается в шаблоне Фабричный метод.

Описание шаблона

Основная идея шаблона в том, чтобы создавать копии уже готовых объектов с помощью самих объектов копирования. Естественно последние должны уметь это делать, обычно это достигается введением в копируемый объект метода clone(), который производит все действия, необходимые для создания копии объекта. Объекты, умеющие делать клонирование, называют прототипами, отсюда и название паттерна.

 Чтобы не привязываться к конкретным классам в Прототипе реализуется создание общего абстрактного класса, в котором объявляется публичный метод clone(). Объекты с возможностью клонирования должны быть наследниками такого класса.

Реализация

Пример использования шаблона реализован на PHP. В этом случае создание шаблона упрощается, т.к. в PHP клонирование обеспечивается на уровне языка ключевым словом clone. Но важно помнить, что подобная операция копирует и все внутренние ссылки копируемого объекта. Получается, что прототип и его копия будут иметь общие поля, если они являются объектами, что редко востребовано. Поэтому для полностью независимой копии после создания клона надо вручную производить клонирование и внутренних объектов. Это производится с помощью магического метода __clone(), который автоматически вызывается при использовании ключевого слова clone. В примере  для того, чтобы показать, как это происходит, в прототип введено объектное свойство $review.

Начнем с создания класса, описывающего товар.

<?php

class Review
{
    private string $descr = 'описание отсутствует';

    public function setReview(string $txt): void
    {
        $this->descr = $txt;
    }

    public function getReview(): string
    {
        return $this->descr;
    }
}

Создадим абстрактный класс, который будет родителем для всех прототипируемых наследников.

abstract class Prototype
{
    abstract public function __clone();
}

Создадим наследника абстрактного класса. Он умеет клонировать себя, умеет работать с описанием товара на основе класса Review.

class Book extends Prototype
{
    private string $name;
    private float $price;
    private string $category;

    private ?Review $review = null;

    public function __construct(
        string $name,
        float $price,
        string $category
    ) {
        $this->name = $name;
        $this->price = $price;
        $this->category = $category;
    }

    public function setName(string $name): void
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function setPrice(float $price): void
    {
        $this->price = $price;
    }

    public function getPrice(): float
    {
        return $this->price;
    }

    public function setReview(Review $review): void
    {
        $this->review = $review;
    }

    public function getReview(): ?Review
    {
        return $this->review;
    }

    /**
     * При клонировании книги создаём независимую копию Review.
     */
    public function __clone()
    {
        if ($this->review !== null) {
            $this->review = clone $this->review;
            $this->review->setReview('описание отсутствует');
        }
    }

    public function getInfo(): string
    {
        $review = $this->review?->getReview() ?? 'описание отсутствует';

        return sprintf(
            'Product - <span class="bold">%s</span>, cost = <span class="bold">%.2f</span>, Краткое описание: <span class="bold">%s</span>',
            htmlspecialchars($this->name, ENT_QUOTES, 'UTF-8'),
            $this->price,
            htmlspecialchars($review, ENT_QUOTES, 'UTF-8')
        );
    }
}

Всё готово для демонстрации шаблона Prototype. Вот код файла index.php, который это проделает.

<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <title>Пример реализации шаблона Прототип на PHP</title>

    <style>
        .bold {
            font-weight: bold;
        }
    </style>
</head>

<body>

<?php

$review = new Review();

$book1 = new Book('Book1', 8.00, 'Книги');
$book1->setReview($review);

// Создаём клон книги.
// Review также будет склонирован благодаря __clone().
$book2 = clone $book1;

// Изменяем данные клона.
$book2->setName('Book2');
$book2->setPrice(10.00);

// Меняем описание только у клона.
$book2->getReview()?->setReview(
    'Эта книга описывает шаблоны программирования.'
);

echo '<span class="bold">';
echo 'Сравниваем два наименования в категории "Книги"';
echo '</span><br>';

echo $book1->getInfo();
echo '<br>';
echo $book2->getInfo();

?>

</body>
</html>

После запроса скрипта index.php в браузере получим следующий отклик.

Сравниваем два наименования в категории "Книги"
Product - Book1, cost = $8, Краткое описание: is absent
Product - Book2, cost = $10, Краткое описание: описание отсутствует

Как видите, мы создали копию объекта, инициализировали ее и получили для приложения новый объект.

Итоги

Итак, вам был представлен порождающий шаблон проектирования Prototype (Прототип), позволяющий создавать новые объекты путём копирования уже существующего объекта-прототипа. На приведенном примере была продемонстрирована работа шаблона.
В итоге хочу указать на преимущества и возможные недостатки шаблона.
Достоинства Prototype.

  • Если объект требует много действий для создания и настройки, проще скопировать уже подготовленный объект и откорректировать нужное.
  • Позволяет создавать объекты динамически.
  • Удобно использовать для объектов со сложной начальной конфигурацией.
  • Уменьшает количество наследников.

Недостатки Прототипа могут сделать применение шаблона не всегда оправданным.

  • Клонирование сложных объектов может быть непростым.
  • Можно случайно получить нежелательные общие объекты.
  • Клонирование не всегда очевидно. Выражение в примере $book2 = clone $book1; не объясняет, было произведено глубокое копирование(реализуется вручную через магический метод __clone()) или поверхностное(copy = clone orig;).

Остается добавить, что выбор создания объекта через new или clone за вами и определяется, как правило, контекстом.

На этом пока всё.

Успехов в разработке!

Перейти к списку шаблонов проектирования.