Также известен как Клон, Prototype

Прототип

Суть паттерна

Позволяет копировать существующие объекты, не вдаваясь в подробности их реализации.

Проблема

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

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

Решение

Паттерн Прототип поручает создание копий самим копируемым объектам.

Он вводит общий интерфейс для всех объектов, поддерживающих клонирование. Это позволяет копировать объекты, не привязываясь к их конкретным классам. Обычно такой интерфейс имеет все один метод clone.

Реализация этого метода в разных классах очень схожа. Метод создаёт новый объект текущего класса и копирует в него значения всех полей объекта. Так получится скопировать даже приватные поля, так как большинство языков программирования разрешает доступ к приватным полям отдельного объекта текущего класса.

Объект, который копируют, называется прототипом (откуда и название паттерна). Когда объекты программы содержат сотни полей и тысячи возможных конфигураций, прототипы могут служить своеобразной альтернативой созданию подклассов. В этом случае, все возможные прототипы заготавливаются и настраиваются на этапе инициализации программы. Потом, когда программе нужен новый объект, она создаёт копию из приготовленного прототипа.

Аналогия из жизни

Деление клетки

В промышленном производстве, прототипы продуктов создаются перед основной партией для проведения всевозможных испытаний. При этом прототип не участвует в последующем производстве, отыгрывая пассивную роль.

Более удачный пример — деление клеток. После митозного деления клеток образуются две совершенно идентичные клетки. Оригинальная клетка отыгрывает роль прототипа, принимает активную роль в создании нового объекта.

Армия клонов Республики

В далёкой-далёкой галактике, Республика, используя ДНК одного крутого наёмника, создала целую армию клонов. Из-за того, что все клоны получились одинаковые, можно было унифицировать процесс подготовки, форму, вооружение и транспорт.

P.S. Надеюсь, вы фанат Звёздных Войн?

Структура

Схема структуры классов паттерна Прототип

Реализация с общим хранилищем прототипов

Вариант Прототипа с общим хранилищем прототипов
  1. Прототип описывает интерфейс операции клонирования. В большинстве случаев, это будет единственный метод clone.

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

  3. Клиент создаёт копию объекта, обращаясь к нему через интерфейс Прототипа.

  4. Хранилище прототипов (опционально) в программе может существовать централизованное хранилище прототипов, облегчающее поиск подходящего прототипа. В простейшем случае — это ассоциативный массив имя-прототипа → прототип.

Псевдокод

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

Таким образом, паттерн Прототип позволяет клиентскому коду клонировать объекты, даже не зная и не завися от их конкретных классов.

// Базовый прототип.
abstract class Shape is
    field X: int
    field Y: int
    field color: string

    // Копирование всех полей объекта происходит в конструкторе.
    method Shape(target: Shape) is
        if (target != null) then
            this.X = target.X;
            this.Y = target.Y;
            this.color = target.color;

    // Результатом операции клонирования всегда будет объект из иерархии
    // классов Shape.
    abstract method clone(): Shape


// Конкретный прототип. Метод клонирования создаёт новый объект и передаёт в его
// конструктор для копирования собственный объект. Этим мы пытаемся получить
// атомарность операции клонирования. В данной реализации, пока не выполнится
// конструктор, нового объекта ещё не существует. Но как только конструктор
// завершён, мы получаем полностью готовый объект-клон, а не пустой объект,
// который нужно ещё заполнить.
class Rectangle extends Shape is
    field width: int
    field height: int

    method Rectangle(target: Rectangle) is
        // Вызов родительского конструктора нужен, чтобы скопировать
        // потенциальные приватные поля, объявленные в родительском классе.
        super(target)
        if (target != null) then
            this.width = target.width;
            this.height = target.height;

    method clone(): Shape is
        return new Rectangle(this)


class Circle extends Shape is
    field radius: int

    method Circle(target: Circle) is
        super(target)
        if (target != null) then
            this.radius = target.radius;

    method clone(): Shape is
        return new Circle(this)


// Где-то в клиентском коде.
class Application is
    field shapes: array of Shape

    method constructor() is
        Circle circle = new Circle();
        circle.X = 10
        circle.Y = 20
        circle.radius = 15
        shapes.add(circle);

        Circle anotherCircle = circle.clone();
        shapes.add(anotherCircle);
        // anotherCircle будет содержать точную копию circle.

        Rectangle rectangle = new Rectangle();
        rectangle.width = 10
        rectangle.height = 20
        shapes.add(rectangle);

    method businessLogic() is
        // Неочевидным плюс Прототипа в том, что вы можете клонировать набор
        // объектов, не зная их конкретных классов.
        Array shapesCopy = new Array of Shapes.

        // Например, мы не знаем какие конкретно объекты находятся внутри
        // массива shapes, так как он объявлен с типом Shape. Но благодаря
        // полиморфизму, мы можем клонировать все объекты «вслепую». Будет
        // выполнен метод clone() того класса, которым является этот объект.
        foreach shapes as shape do
            shapesCopy.add(shape.clone())

        // shapesCopy будет содержать точные копии элементов массива shapes.

Применимость

Ваш код не должен зависеть от создаваемых продуктов. Например, если тип создаваемого продукта нельзя определить наперёд или он может изменятся во время выполнения.

Паттерн прототип предоставляет клиенту общий интерфейс для работы со всеми прототипами.

Клиенту не нужно зависеть от всех классов копируемых объектов, а только от интерфейса клонирования.

Вам нужно избежать разрастания иерархии классов продуктов, причём продукты отличаются только внутренним состоянием.

Паттерн прототип предлагает создать набор объектов-прототипов, вместо создания иерархии подклассов продуктов.

Таким образом, вместо прямого создания объектов из подклассов, вы будете копировать существующие объекты-прототипы, в которых уже настроено внутреннее состояние.

Это позволит избежать взрывного роста количества классов в программе и уменьшить её сложность.

Шаги реализации

  1. Добавьте операцию clone в вашу иерархию продуктов.

    Если такой иерархии нет, создайте новый интерфейс клонирования и поместите туда метод клонирования. Затем реализуйте этот интерфейс во всех продуктах, которые нужно будет клонировать.

  2. Создайте «каталог» объектов-прототипов, из которых будут создаваться копии. Это вариации объектов, возможно даже одного типа, но по-разному настроенных. Каталог может быть инкапсулирован либо в новом фабричном классе, либо в фабричном методе базового класса продуктов.

  3. Такой фабричный метод должен на основании входящих аргументов искать в каталоге прототипов подходящий экземпляр, а затем вызывать его метод клонирования и возвращать полученный объект.

  4. В клиентском коде можно заменить все вызовы конструктора продуктов вызовами нового фабричного метода.

Преимущества и недостатки

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

Отношения с другими паттернами

Реализация в различных языках программирования

Java