Prototype (Прототип)¶
Категория: порождающий паттерн.
Проблема¶
Нужно создавать объекты как копии уже настроенного экземпляра, но клиент не должен зависеть от конкретного класса и знать обо всех его полях. Копирование снаружи ломает инкапсуляцию, не видит закрытое состояние и быстро устаревает при изменении класса.
Решение¶
Копирование поручается самому объекту-прототипу. Общий метод Clone() создаёт экземпляр того же фактического типа и переносит в него всё необходимое состояние. Клиент работает через интерфейс прототипа и не знает, какой конкретный класс клонируется.
Структура¶
Prototype- общий контракт клонирования.ConcretePrototype- знает, как скопировать собственные, в том числе закрытые, поля.Client- запрашивает копию через общий контракт.PrototypeRegistry(опционально) - хранит заранее настроенные эталонные экземпляры по ключу и выдаёт их копии.
Варианты реализации¶
- Конструктор копирования - типобезопасный и явный вариант:
new Document(source). Каждый уровень иерархии копирует объявленные им поля. - Полиморфный
Clone()- позволяет клонировать коллекцию объектов через общий интерфейс, не проверяя их конкретные типы. - Реестр прототипов - полезен, когда есть набор популярных конфигураций: вместо множества подклассов клиент просит копию именованного шаблона.
- Поверхностная или глубокая копия - поверхностная копирует ссылки, глубокая создаёт новые изменяемые вложенные объекты. Это не два разных паттерна, а важный выбор семантики клонирования.
В .NET не следует бездумно использовать MemberwiseClone() или устаревший ICloneable: первый делает только поверхностную копию, а второй не сообщает вызывающему коду, какую именно копию он получит. Обычно понятнее собственный типизированный контракт, конструктор копирования или record-выражение with для моделей со значимой семантикой значения.
Когда применять¶
- Конкретные классы создаваемых объектов неизвестны клиенту или приходят извне через общий интерфейс.
- Настройка нового экземпляра дороже или сложнее, чем копирование готового.
- Много подклассов отличаются только начальными значениями полей и могут быть заменены набором настроенных прототипов.
Плюсы¶
- Клиент не зависит от конкретных классов клонируемых объектов.
- Повторно используется сложная конфигурация без дублирования кода инициализации.
- Реестр прототипов позволяет добавлять новые шаблоны во время выполнения.
Минусы¶
- Глубокое копирование графов с циклами и разделяемыми ссылками сложно реализовать корректно.
- Неочевидная семантика поверхностной копии приводит к случайному общему изменяемому состоянию.
- При добавлении нового поля нужно не забыть включить его в копирование.
Отличие от Builder¶
Builder создаёт объект по шагам из переданных параметров. Prototype начинает с уже готового состояния и получает новый объект копированием. Их можно сочетать: Builder подготавливает эталон, а Prototype быстро размножает его.
Пример в .NET / C¶
recordи выражениеwithсоздают неглубокую копию с изменением выбранных компонентов.Array.Clone()создаёт поверхностную копию массива.MemberwiseClone()доступен наследникамobject, но требует явной обработки вложенного изменяемого состояния.
Источник для сверки структуры: Refactoring.Guru - Prototype.