Patterns. Паттерн Prototype. Реалізація структури на C++

Паттерн Prototype. Реалізація структури на C++

1. Загальні відомості. Призначення. Відношення

Паттерн Prototype належить до паттернів, які породжують об’єкти. Паттерн дозволяє створювати об’єкти різних типів з допомогою об’єкту-прототипу. Цей об’єкт повертає копію конкретного об’єкту заданого типу.

Структура паттерну Prototype зображена на рисунку 1.

Паттерн Prototype. Структурна схема

Рисунок 1. Структура паттерну Prototype

Відношення: клієнт робить запит до прототипу, щоб отримати його копію.

 

2. Застосування

Паттерн Prototype доцільно застосовувати у наступних випадках.

  1. Коли створення та представлення кінцевих продуктів не впливають на систему в цілому.
  2. Екземпляри класів продуктів потрібно визначити на етапі виконання з допомогою поліморфізму.
  3. Коли є зайвим створення ієрархій класів чи їх фабрик, які є паралельними до ієрархії класів-продуктів.
  4. Якщо потрібно щоб об’єкт класу міг перебувати в одному з декількох різних станів. У цьому випадку створюється відповідна кількість прототипів та реалізується їх клонування.

 

3. Учасники паттерну. Приклад програми на мові C++. Реалізація структури (рисунок 1)

У прикладі реалізовано паттерн Prototype, структура якого зображена на рисунку 1.

Учасниками паттерну є наступні класи:

  • Prototype – задає інтерфейс для клонування самого себе;
  • ConcretePrototype – реалізує операцію (метод) клонування себе;
  • Client – з допомогою прототипу (prototype) задає запит для створення нового об’єкту для клонування самого себе. У програмі клієнтом виступає функція main().
#include <iostream>
using namespace std;

// Паттерн Prototype - реалізція структури

// Абстрактний клас прототипу,
// підкласи цього класу будуть клонувати самі себе
class Prototype abstract
{
public:
  virtual Prototype* Clone() abstract;

  // Додатковий метод для виведення інформації про клас
  virtual void Print(string) abstract;
};

// Класи конкретних прототипів
// Конкретний прототип 1
class ConcretePrototype1 : public Prototype
{
private:
  string data; // дані класу

public:
  // Конструктор
  ConcretePrototype1(string data) : data(data)
  { }

  Prototype* Clone() override
  {
    // Створити копію в пам'яті
    Prototype* p = new ConcretePrototype1(data);

    // Повернути копію
    return p;
  }

  // Метод виведення інформації про клас
  void Print(string msg) override
  {
    cout << msg << " => " << data << endl;
  }
};

// Конкретний прототип 2
class ConcretePrototype2 : public Prototype
{
private:
  string data; // дані класу

public:
  // Конструктор
  ConcretePrototype2(string data) : data(data)
  { }

  // Метод клонування
  virtual Prototype* Clone() override
  {
    // Створити копію в пам'яті
    Prototype* p = new ConcretePrototype2(data);

    // Повернути копію
    return p;
  }

  // Метод виведення інформації про клас
  void Print(string msg) override
  {
    cout << msg << " => " << data << endl;
  }
};

void main()
{
  // Клієнтський код
  // Ввести номер класу-прототипу
  int numPrototype;
  cout << "numPrototype = ";
  cin >> numPrototype;

  Prototype* p = nullptr;

  // В залежності від numPrototype отримати відповідну копію
  if (numPrototype == 1)
  {
    // Створити об'єкт прототипу
    ConcretePrototype1 prototype1("prototype1");

    // Отримати копію
    p = prototype1.Clone();
  }
  else
  {
    // Створити об'єкт прототипу
    ConcretePrototype2 prototype2("prototype2");
    p = prototype2.Clone();
  }

  // Вивести інформацію про об'єкт прототипу
  p->Print("p");

  // Звільнити раніше виділену пам'ять
  if (p)
    delete p;
}

 

4. Результати

Результати використання паттерну Prototype дають наступні переваги:

  • паттерн приховує від клієнта конкретні класи продуктів. Це зменшує кількість імен, які відомі клієнту;
  • паттерн дає можливість клієнту працювати з вузькоспеціалізованими класами без додаткових модифікацій;
  • додавання та видалення продуктів може відбуватись під час виконання. Тут у клієнта з’являється додаткова гнучкість порівняно з іншими породжуючими паттернами;
  • нові об’єкти створюються за рахунок зміни їх поведінки (зміна значень змінних об’єкту) а не за рахунок визначення нових класів. Іншими словами, на основі вже існуючих класів створюються екземпляри нових видів клієнтських об’єктів;
  • існує можливість визначення нових об’єктів шляхом зміни структури. Це є побудова об’єктів з різних складових з допомогою клонування;
  • зменшення кількості підкласів порівняно з іншими паттернами (Factory Method, Abstract Factory);
  • з допомогою класів відбувається динамічне конфігурування додатку. Класи підвантажуються в додаток під час виконання.

Недоліком паттерну вважається складність реалізації Clone() у деяких випадках. Прикладом може бути наявність інших підоб’єктів у даному об’єкті.

 

5. Реалізація

При реалізації паттерну Prototype можна виділити такі основні питання.

  1. Використання диспетчера прототипів. Тут мається на увазі ведення реєстру прототипів для його використання клієнтами. Клієнти зберігають прототипи в реєстрі та витягують їх з нього на основі заданого ключа.
  2. Складність реалізації методу Clone() у випадку, якщо в об’єкт є складною структурою з циклічними посиланнями. Тут важливо виконати глубоке копіювання для елементів, що входять в структуру складного об’єкту. При цьому слід враховувати таку саму послідовність кроків при звільненні копії об’єкту.
  3. Ініціалізація клону значеннями. Тут можливі ситуації, коли для різних клонованих об’єктів потрібно отримувати різні значення параметрів.