PHP Prototype Design Pattern Порождающий паттерн

Паттерн Прототип в PHP

Короткий скринкаст: паттерн «Прототип» (Prototype) — клонируем объект через clone и __clone() без повторного вызова конструктора.

Prototype Pattern — открыть страницу урока

Описание

В скринкасте мы клонируем отчёт. Обычный clone в PHP копирует «по ссылке» вложенные объекты, поэтому меняется и оригинал. Метод __clone() делает копию глубокой: автор клонируется, а клон получает свежий id — оригинал остаётся нетронутым.

UML-диаграмма

classDiagram class Author { +string name } class Report { -int id -string title -Author author -array sections +__construct(id: int, title: string, author: Author, sections: array) +addSection(section: string) void +describe() string +__clone() } Report *-- Author : contains Report ..> Report : clones

Код

Author.php

class Author
{
    public function __construct(public string $name) {}
}

Report.php

class Report
{
    private static int $nextId = 2;

    public function __construct(
        public int $id,
        public string $title,
        public Author $author,
        public array $sections = [],
    ) {}

    public function addSection(string $section): void
    {
        $this->sections[] = $section;
    }

    public function describe(): string
    {
        return "#{$this->id} {$this->title} by {$this->author->name}: " . implode('; ', $this->sections);
    }

    public function __clone()
    {
        $this->id = self::$nextId++;
        $this->author = clone $this->author;
    }
}

demo.php

require_once 'Author.php';
require_once 'Report.php';

$base = new Report(1, 'Q3 Report', new Author('Anna'), ['intro']);

$copy = clone $base;
$copy->title = 'Q3 Report (rev)';
$copy->author->name = 'Bob';
$copy->addSection('summary');

echo $base->describe();
echo PHP_EOL;
echo $copy->describe();

Заметка: почему __clone()

Без __clone() клонирование в PHP «поверхностное»: $base->author и $copy->author — один и тот же объект. Переименовать автора в копии — значит переименовать и в оригинале. __clone() вклинивается после копирования полей и позволяет доклонировать вложенные объекты, а идентификатор — взять свежий из счётчика. Так клон получает собственное состояние и не ломает прототип.

Пример результата

После php demo.php в терминале появится:

#1 Q3 Report by Anna: intro
#2 Q3 Report (rev) by Bob: intro; summary

Преимущества

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

Когда использовать

  • Когда создание объекта дороже, чем его копирование.
  • Когда нужно много похожих объектов с небольшими отличиями.
  • Когда конкретные классы задаются во время выполнения.
  • Когда важно избежать жёсткой иерархии фабрик.

Использование в Laravel

В Laravel Прототип — это клонирование моделей: Illuminate\Database\Eloquent\Model::replicate() создаёт копию модели с теми же атрибутами, которую дальше меняют перед сохранением. Тот же приём используется при клонировании HTTP-запроса (clone $request) и в тестах, где копия объекта настраивается отдельно.