Прототип (Prototype)

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

Сигналы применения

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

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

Роли

  • Prototype объявляет операцию копирования;
  • Concrete Prototype определяет глубину и правила копии;
  • Registry (необязательно) хранит именованные прототипы;
  • Client получает основу, копирует и меняет только нужные параметры.

Пример на Java

final class SimulationPreset {
    private final String algorithm;
    private final List<Integer> checkpoints;
    SimulationPreset(String algorithm, List<Integer> checkpoints) {
        this.algorithm = algorithm;
        this.checkpoints = new ArrayList<>(checkpoints);
    }
    // Явный copy constructor: правила копирования видны в коде.
    SimulationPreset(SimulationPreset source) { this(source.algorithm, source.checkpoints); }
    SimulationPreset withCheckpoint(int value) {
        var copy = new SimulationPreset(this);
        copy.checkpoints.add(value);
        return copy;
    }
    List<Integer> checkpoints() { return List.copyOf(checkpoints); }
}
var baseline = new SimulationPreset("stable", List.of(10, 50));
var extended = baseline.withCheckpoint(90);

Список копируется, поэтому изменение extended не затрагивает baseline. Для графа объектов нужно заранее решить: копировать всё, разделять неизменяемые узлы или сохранять идентичность отдельных ссылок.

Java: почему осторожно с Cloneable

Cloneable — маркер без метода clone(). Сам Object.clone() защищённый, делает поверхностную копию и связан с CloneNotSupportedException. Конструктор при этом не выполняется, а правила для вложенных полей остаются неочевидными.

Для большинства domain objects понятнее явный copy constructor или copy factory: SimulationPreset.copyOf(source). Такой API типобезопасен, позволяет валидировать инварианты и явно задаёт глубокое или поверхностное копирование.

Компромиссы

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

Чем отличается

  • Строитель Builder описывает создание шагами, Prototype берёт готовую основу;
  • Фабричный метод Factory Method скрывает выбор класса, но обычно создаёт объект по правилам фабрики, а не из состояния экземпляра;
  • сериализация иногда даёт глубокую копию, но это тяжёлый механизм и не замена осмысленному контракту.

Вопросы на собеседовании

  1. Чем глубокая копия отличается от поверхностной? Глубокая создаёт независимые изменяемые вложенные объекты; поверхностная копирует ссылки.
  2. Когда разделять ссылку безопасно? Когда объект неизменяемый либо разделение прямо входит в контракт.
  3. Почему Cloneable считают неудобным? У него нет публичного метода копирования, а стандартный clone() поверхностный и имеет необычный контракт.
  4. Нужен ли registry? Нет, но он удобен для набора именованных основ.
  5. Как проверить реализацию? Изменить копию и убедиться, что оригинал сохраняет состояние и инварианты.

Связи: Порождающие паттерны, Коллекции, Шаблоны проектирования.