Lapa Knowledge Base
Порождающие паттерны

Prototype

Паттерн Prototype

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

Является паттерном порождающего типа.

Для чего используется?

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

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

Когда удобно применять:

  • Класс объекта определяется динамически прямо во время выполнения программы;
  • Не требуется выстраивать систему наследования создателя объекта;
  • Создание объекта дороже и сложнее копирования существующего, и в объект не нужно вносить больших изменений;
  • Нужно создать объекты с похожей структурой, но разными данными.

Варианты кейсов использования:

  • Генерация тестовых данных;
  • Автоматизация развёртывания серверов и сервисов;
  • Быстрое масштабирование инфраструктуры.

Какова механика реализации?

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

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

Структура и отношения

Prototype
└── clone(): Prototype

ConcretePrototype1
├── field1: string
└── clone(): Prototype

ConcretePrototype2
├── field1: string
└── clone(): Prototype

Client
└── operation(prototype: Prototype): void

Пример реализации

// Базовый прототип
class Shape {
  constructor() {
      this.x = 0;
      this.y = 0;
      this.color = 'black';
  }
  
  clone() {
      throw new Error('Метод clone должен быть переопределен');
  }
  
  render() {
      throw new Error('Метод render должен быть переопределен');
  }
}

// Конкретные прототипы
class Circle extends Shape {
  constructor(radius = 1) {
      super();
      this.radius = radius;
  }
  
  clone() {
      const cloned = new Circle(this.radius);
      cloned.x = this.x;
      cloned.y = this.y;
      cloned.color = this.color;
      return cloned;
  }
  
  render() {
      return `Отрисовка круга: радиус=${this.radius}, цвет=${this.color}, позиция=(${this.x}, ${this.y})`;
  }
}

class Rectangle extends Shape {
  constructor(width = 1, height = 1) {
      super();
      this.width = width;
      this.height = height;
  }
  
  clone() {
      const cloned = new Rectangle(this.width, this.height);
      cloned.x = this.x;
      cloned.y = this.y;
      cloned.color = this.color;
      return cloned;
  }
  
  render() {
      return `Отрисовка прямоугольника: размер=${this.width}x${this.height}, цвет=${this.color}, позиция=(${this.x}, ${this.y})`;
  }
}

// Менеджер прототипов
class ShapeManager {
  constructor() {
      this.shapes = {};
  }
  
  addShape(key, shape) {
      this.shapes[key] = shape;
  }
  
  getShape(key) {
      return this.shapes[key].clone();
  }
}

// Использование
const manager = new ShapeManager();

// Создаем прототипы
const circlePrototype = new Circle(5);
circlePrototype.color = 'red';
circlePrototype.x = 10;
circlePrototype.y = 20;

const rectanglePrototype = new Rectangle(10, 5);
rectanglePrototype.color = 'blue';
rectanglePrototype.x = 30;
rectanglePrototype.y = 40;

// Регистрируем прототипы
manager.addShape('red-circle', circlePrototype);
manager.addShape('blue-rectangle', rectanglePrototype);

// Создаем копии
const circle1 = manager.getShape('red-circle');
const circle2 = manager.getShape('red-circle');
const rectangle1 = manager.getShape('blue-rectangle');

// Изменяем копии независимо
circle1.x = 100;
circle2.y = 200;
rectangle1.color = 'green';

console.log(circle1.render());
console.log(circle2.render());
console.log(rectangle1.render());

Преимущества

  • Объекты клонируются в изоляции от конкретных классов - код отработает вне зависимости от класса-инициализатора;
  • Клонирование объектов требует меньших ресурсов и времени, чем их создание;
  • Можно в любой момент создать нужное количество объектов, копируя существующие.

Недостатки

  • В зависимости от объекта, копирование может быть ресурсозатратным. Некоторые объекты содержат крупные, "тяжелые" данные;
  • Есть вероятность клонирования защищенных данных с угрозой для их безопасности;
  • Составные объекты, имеющие ссылки на другие объекты, сложно и запутанно копировать.

Отличия от других паттернов

  • Factory Method — его прямой конкурент. Фабричный метод построен на наследовании, но не требует сложной инициализации - в то время как Прототип, напротив, не требует наследования, но объект-Прототип нужно правильно настроить изначально.
  • Abstract Factory — похож на Прототип, но создает интерфейсы для продуктов, составляющих семейства.

Реальный пример

// Прототип для создания пользовательских интерфейсов
class UIComponent {
    constructor(type, props = {}) {
        this.type = type;
        this.props = { ...props };
        this.children = [];
    }
    
    clone() {
        const cloned = new UIComponent(this.type, this.props);
        cloned.children = [...this.children];
        return cloned;
    }
    
    addChild(child) {
        this.children.push(child);
        return this;
    }
    
    render() {
        return `<${this.type} ${Object.entries(this.props).map(([k, v]) => `${k}="${v}"`).join(' ')}>${this.children.map(child => child.render()).join('')}</${this.type}>`;
    }
}

// Создаем прототипы компонентов
const buttonPrototype = new UIComponent('button', { class: 'btn', type: 'button' });
const inputPrototype = new UIComponent('input', { type: 'text', class: 'form-control' });
const divPrototype = new UIComponent('div', { class: 'container' });

// Создаем копии и настраиваем их
const submitButton = buttonPrototype.clone();
submitButton.props.value = 'Отправить';
submitButton.props.class = 'btn btn-primary';

const cancelButton = buttonPrototype.clone();
cancelButton.props.value = 'Отмена';
cancelButton.props.class = 'btn btn-secondary';

const form = divPrototype.clone();
form.props.class = 'form-group';

const nameInput = inputPrototype.clone();
nameInput.props.placeholder = 'Введите имя';
nameInput.props.name = 'name';

const emailInput = inputPrototype.clone();
emailInput.props.placeholder = 'Введите email';
emailInput.props.name = 'email';
emailInput.props.type = 'email';

// Собираем форму
form.addChild(nameInput);
form.addChild(emailInput);
form.addChild(submitButton);
form.addChild(cancelButton);

console.log(form.render());

Prototype — это элегантный способ создания объектов путем клонирования!

Copyright © 2026