Также известен как Клон, 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
        Call parent constructor
        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
        Call parent constructor
        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