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

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

Паттерн позволяет в клиентском коде не заниматься подготовкой целевого объекта к использованию или же снизить объём подготовки, нежели чем это было бы при создании объекта с помощью стандартной языковой директивы конструктора new (PHP, C#) или метода сreate (Delphi).

Пример задачи: создание армии юнитов на игровом поле военной стратегии.

Далее приводится реализация паттерна на языке PHP. Скачать исходники (ZIP, 3 Кб)

army_units.php

Содержит классы юнитов:
  • ArmyUnitPrototype – абстрактный класс, который представляет собой прототип игрового человечка (юнита). В прототипе объявлены и определены общие для всех игровых юнитов свойства и методы. Таким образом, прототип служит для удобного разделения конечных юнитов по видам, где каждый вид содержит свои настройки (например, по уровню жизни и силе удара).

    Так же в данном классе определён метод getCopy, который сначала создаёт пустой объект конечного класса, а затем наполняет его свойствами. Обратите внимание на фрагмент new static(), использующий позднее статическое связывание при создании объекта;
  • KnightUnit – класс юнита «Рыцарь», являющийся наследником прототипа (абстрактного класса ArmyUnitPrototype);
  • ArcherUnit – класс юнита «Лучник», наследник прототипа;
  • CitizenUnit – класс юнита «Гражданский», наследник прототипа.
<?php

/**
 * Абстрактный класс армейского юнита
 */
abstract class ArmyUnitPrototype
{
    /**
     * Название юнита
     * @var string
     */
    protected $name;

    /**
     * Уровень жизни
     * @var int
     */
    protected $lifeLevel;

    /**
     * Сила удара (мощность выстрела)
     * @var int
     */
    protected $shotPower;

    /**
     * Создаёт и возвращает копию текущего объекта
     * @return ArmyUnitPrototype
     */
    public function getCopy()
    {
        /*
        Создаём новый объект. Обратите внимание на использование конструкции static, 
        которая используется для позднего статического связывания. 
        Подробнее: https://www.php.net/manual/ru/language.oop5.late-static-bindings.php
        */
        $newUnit = new static();
        
        //Получаем свойства текущего объекта и прописываем их значения в новый объект
        $arProps = get_object_vars($this);
        foreach ($arProps as $prop => $value) {
            $newUnit->$prop = $value;
        }

        //Возвращаем новый объект
        return $newUnit;
    }

    /**
     * Устанавливает название юнита
     * @param string $name - название юнита
     */
    public function setName($name)
    {
        $this->name = $name;
    }

    /**
     * Устанавливает уровень жизни юнита
     * @param int $value - значение силы удара
     */
    public function setLifeLevel($value)
    {
        $this->lifeLevel = (int)$value;
    }

    /**
     * Устанавливает силу удара юнита
     * @param int $value - значение силы удара
     */
    public function setShotPower($value)
    {
        $this->shotPower = (int)$value;
    }

    /**
     * Возвращает строку со сведениями о юните (имя, уровень жизни и силу удара)
     * @return string
     */
    public function getInfo()
    {
        return $this->name.' | жизнь = '.$this->lifeLevel.' | удар = '.$this->shotPower;
    }
}

/**
 * Класс юнита "Рыцарь"
 */
class KnightUnit extends ArmyUnitPrototype
{
    public function __construct()
    {
        $this->name = 'Рыцарь';
        $this->lifeLevel = 200;
        $this->shotPower = 50;
    }
}

/**
 * Класс юнита "Лучник"
 */
class ArcherUnit extends ArmyUnitPrototype
{
    public function __construct()
    {
        $this->name = 'Лучник';
        $this->lifeLevel = 120;
        $this->shotPower = 30;
    }
}

/**
 * Класс юнита "Гражданский"
 */
class CitizenUnit extends ArmyUnitPrototype
{
    public function __construct()
    {
        $this->name = 'Гражданский';
        $this->lifeLevel = 100;
        $this->shotPower = 10;
    }
}

?>

test_copy.php

Содержит тестовый скрипт, в котором создаются юниты трёх видов: Рыцарь, Лучник и Гражданский. Затем у гражданских юнитов прокачивается навык «Самооборона», который повышает их жизнь и силу удара. Далее с помощью метода getCopy создаётся копия Рыцаря, Лучника и Гражданского. Потом копированием создаётся особый тип юнита «Шпион». 
<?php

//Подключение классов юнитов
require_once(__DIR__.'/army_units.php');

//Рыцарь
$knight = new KnightUnit();  

//Лучник
$archer = new ArcherUnit();

//Гражданский
$citizen = new CitizenUnit();

/*
У гражданских прокачан навык "Самооборона", который повышает
уровень жизни и силу удара
*/
$citizen->setLifeLevel(115);
$citizen->setShotPower(20);

//Создаём копии юнитов, используя метод getCopy
$knight_copy = $knight->getCopy();
$archer_copy = $archer->getCopy();
$citizen_copy = $citizen->getCopy();

//Некоторые гражданские являются шпионами, у них уровень жизни выше гражданского
$citizen_spy = $citizen->getCopy();
$citizen_spy->setName('Шпион');
$citizen_spy->setLifeLevel(140);

echo $knight_copy->getInfo()."<br>";
echo $archer_copy->getInfo()."<br>";
echo $citizen_copy->getInfo()."<br>";
echo $citizen_spy->getInfo()."<br>";

?>

В конце скрипта с помощью метода getInfo выводится информация об уровне жизни и силе удара для копии Рыцаря, Лучника, Гражданского и Шпиона.
Рыцарь | жизнь = 200 | удар = 50
Лучник | жизнь = 120 | удар = 30
Гражданский | жизнь = 115 | удар = 20
Шпион | жизнь = 140 | удар = 20

test_clone.php

Содержит код, идентичный содержимому скрипта test_copy.php. Единственное различие, что для копирования объектов вместо метода getCopy используется стандартный для PHP оператор клонирования объектов clone.
<?php

//Подключение классов юнитов
require_once(__DIR__.'/army_units.php');

//Рыцарь
$knight = new KnightUnit();  

//Лучник
$archer = new ArcherUnit();

//Гражданский
$citizen = new CitizenUnit();

/*
У гражданских прокачан навык "Самооборона", который повышает
уровень жизни и силу удара
*/
$citizen->setLifeLevel(115);
$citizen->setShotPower(20);

//Создаём копии юнитов, используя стандартный оператор клонирования объекта clone
$knight_copy = clone $knight;
$archer_copy = clone $archer;
$citizen_copy = clone $citizen;

//Некоторые гражданские являются шпионами, у них уровень жизни выше гражданского
$citizen_spy = clone $citizen;
$citizen_spy->setName('Шпион');
$citizen_spy->setLifeLevel(140);

echo $knight_copy->getInfo()."<br>";
echo $archer_copy->getInfo()."<br>";
echo $citizen_copy->getInfo()."<br>";
echo $citizen_spy->getInfo()."<br>";

?>

Результат выполнения скрипта (идентичен результату test_copy.php):
Рыцарь | жизнь = 200 | удар = 50
Лучник | жизнь = 120 | удар = 30
Гражданский | жизнь = 115 | удар = 20
Шпион | жизнь = 140 | удар = 20