Прототип (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 скрывает выбор класса, но обычно создаёт объект по правилам фабрики, а не из состояния экземпляра;
- сериализация иногда даёт глубокую копию, но это тяжёлый механизм и не замена осмысленному контракту.
Вопросы на собеседовании
- Чем глубокая копия отличается от поверхностной? Глубокая создаёт независимые изменяемые вложенные объекты; поверхностная копирует ссылки.
- Когда разделять ссылку безопасно? Когда объект неизменяемый либо разделение прямо входит в контракт.
- Почему
Cloneableсчитают неудобным? У него нет публичного метода копирования, а стандартныйclone()поверхностный и имеет необычный контракт. - Нужен ли registry? Нет, но он удобен для набора именованных основ.
- Как проверить реализацию? Изменить копию и убедиться, что оригинал сохраняет состояние и инварианты.
Связи: Порождающие паттерны, Коллекции, Шаблоны проектирования.